Routing do akcji kontrolera w ASP.NET Core

Ryan Nowak, Kirk Larkin i Rick Anderson

Note

Nie jest to najnowsza wersja tego artykułu. Aby uzyskać bieżącą wersję, zobacz artykuł w wersji .NET 10.

Warning

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz .NET i .NET Core Support Policy. Aby uzyskać bieżącą wersję, zobacz artykuł w wersji .NET 10.

kontrolery ASP.NET Core używają middleware, 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.
  • Określa domyślny system routingu jako 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 standardowy kod routingu podobny do następującego przykładu:

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

Użyj polecenia MapControllerRoute , aby utworzyć jedną trasę. Jedyną trasą jest trasa default. 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. Aby uzyskać szczegółowy opis składni szablonu trasy, zobacz Dokumentacja 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() { ... }
}

Przy użyciu powyższej definicji kontrolera i szablonu trasy akcja HomeController.Index jest uruchamiana dla następujących ścieżek URL:

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

Ścieżka URL / używa szablonu trasy z domyślnym kontrolerem Home i akcją Index. Ścieżka adresu URL /Home używa domyślnej Index akcji szablonu trasy.

Metoda MapDefaultControllerRoutewygody:

app.MapDefaultControllerRoute();

Replaces:

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

Important

Routing jest konfigurowany za pomocą oprogramowania pośredniczącego UseRouting i UseEndpoints. Aby użyć kontrolerów:

Aplikacje zazwyczaj nie muszą wywoływać UseRouting ani UseEndpoints. WebApplicationBuilder konfiguruje potok middleware, który otacza składniki middleware dodane za pomocą Program.cs metod UseRouting i UseEndpoints. Aby uzyskać więcej informacji, zobacz Routing w ASP.NET Core.

Routing konwencjonalny

Używaj konwencjonalnego routingu z kontrolerami i widokami. Trasa default przebiega następująco:

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

Powyższy kod 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 odpowiada encji 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.

Korzystając z konwencjonalnego routingu z trasą domyślną, nie musisz tworzyć 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.

Warning

Szablon trasy definiuje id jako opcjonalny. 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:

  • Wiązanie modelu ustawia id na 0.
  • 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

Możesz skonfigurować wiele konwencjonalnych tras , dodając więcej wywołań do MapControllerRoute i MapAreaControllerRoute. Po dodaniu tych wywołań można zdefiniować wiele konwencji lub dodać konwencjonalne trasy przeznaczone do określonej akcji, takie 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ą. Jest to dedykowana trasa konwencjonalna, ponieważ:

Ponieważ szablon trasy "blog/{*article}" nie zawiera parametrów controller i action:

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

W powyższym przykładzie:

  • Trasa blog ma wyższy priorytet dla dopasowań niż default trasa, ponieważ dodajesz ją jako pierwszą.
  • Jest to przykład routingu w stylu Slug , w którym zazwyczaj jest używana nazwa artykułu jako część adresu URL.

Warning

W ASP.NET Core routing nie działa w 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

Routing konwencjonalny odpowiada tylko kombinacji akcji i kontrolera zdefiniowanego przez aplikację. Takie podejście upraszcza przypadki nakładania się konwencjonalnych tras. Podczas dodawania tras za pomocą MapControllerRoute, MapDefaultControllerRoute i MapAreaControllerRoute punkty końcowe automatycznie otrzymują wartość kolejności na podstawie kolejności wywoływania tych metod. Dopasowania z trasy wyświetlanej wcześniej na liście mają wyższy priorytet. Routing konwencjonalny zależy od kolejności. Ogólnie rzecz biorąc, umieść trasy z obszarami wcześniej, ponieważ są bardziej szczegółowe niż trasy bez obszaru. Dedykowane trasy konwencjonalne z parametrami trasy przechwytującymi wszystko, takimi jak {*article}, mogą sprawić, że trasa stanie się zbyt zachłanna. Żarłoczna ścieżka dopasowuje adresy URL, które powinny być obsługiwane przez inne ścieżki. Umieść chciwe trasy później w tabeli tras, aby zapobiec chciwym dopasowaniom.

Warning

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

  • Trasa ogólna, 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 na przykład przypadki, w których wystąpiła ta usterka.

Poprawka opcjonalna tego błędu jest zawarta w .NET Core 3.1.301 lub nowszym SDK. 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 pasują do siebie w ramach routingu, routing musi wykonać jedną z poniższych czynności:

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

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 konwencjonalnym routingu akcje często używają tej samej nazwy akcji, gdy są częścią przepływu pracy obejmującego wyświetlanie i przesyłanie formularza. Na przykład zobacz Zbadaj dwie metody akcji Edytuj.

Jeśli routing nie może wybrać najlepszego kandydata, zgłasza wyjątek AmbiguousMatchException i wyświetla listę wielu pasujących 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 wywołujesz MapControllers, aby mapować kontrolery z routingiem opartym na atrybutach.

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 są zastrzeżonymi nazwami parametrów trasy podczas korzystania z kontrolerów lub stron Razor:

  • action
  • area
  • controller
  • handler
  • page

Używanie page jako parametru trasy w trasowaniu przez atrybuty jest typowym błędem. Ten wybór powoduje 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);
    }
}

Generowanie adresów URL używa tych specjalnych nazw parametrów, aby określić, czy operacja generowania adresu URL odwołuje 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

Nie używaj tych słów kluczowych do generowania linków, parametrów powiązanych modelu ani właściwości najwyższego poziomu.

Szablony czasowników HTTP

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

Szablony tras

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

Routing atrybutów z atrybutami 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, więc id dołącza go do szablonu "api/[controller]" 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}i tak dalej.
    [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 API REST rzadko trzeba używać [Route(...)] w metodzie akcji, ponieważ metoda akcji obsługuje wszystkie metody HTTP. Użyj 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. Ten projekt oznacza, że wiele operacji, takich jak GET i POST w tym samym zasobie logicznym, używa tego samego adresu URL. Routing według atrybutu zapewnia poziom kontroli potrzebny, aby starannie zaprojektować publiczne punkty końcowe 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);
    }
}

Użyj nazw tras, aby wygenerować adres 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 routing atrybutów był mniej powtarzalny, połącz atrybuty trasy na kontrolerze z atrybutami trasy w poszczególnych akcjach. Szablony tras zdefiniowane na kontrolerze są dołączane do szablonów tras w akcjach. W przypadku umieszczenia atrybutu trasy na kontrolerze 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 stosowanymi 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();
    }
}

Poniższa tabela wyjaśnia atrybuty [Route] w powyższym kodzie.

Attribute Łączy się z [Route("Home")] Definiuje wzorzec trasy
[Route("")] Yes "Home"
[Route("Index")] Yes "Home/Index"
[Route("/")] Nie ""
[Route("About")] Yes "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 przy użyciu powyższego kodu 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 słabego projektu routingu. Ilustruje właściwość Order.
  • Właściwość Order rozwiązuje tylko niejednoznaczność. Nie można dopasować tego szablonu. Lepiej jest usunąć [Route("Home")] szablon.

Aby uzyskać informacje o kolejności tras w Razor Pages, zobacz Razor Konwencje tras i aplikacji w Pages: kolejność tras.

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 atrybutowe obsługują zastępowanie tokenów przez ujęcie tokenu w nawiasy kwadratowe ([, ]). Tokeny [action], [area]i [controller] są zastępowane wartościami nazwy akcji, nazwy obszaru i nazwy kontrolera z akcji, w której definiujesz 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}

Zamiana 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 tym wątku dyskusji na GitHubie, jeśli chcesz zobaczyć komentarze kodu w swoim języku ojczystym.

Możesz również połączyć trasy atrybutów z dziedziczeniem. Ta kombinacja jest potężna, gdy używasz zastępowania tokenów. 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 funkcji przekształcania 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:

  • Zastosuj transformator parametru do wszystkich tras atrybutów w aplikacji.
  • Dostosowuje wartości tokenów w trasach atrybutów, gdy są one zastępowane.
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();

Definicję terminu „slug” znajdziesz w dokumentacji MDN Web Docs dotyczącej terminu „slug”.

Warning

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.

Aby uzyskać szczegółowy opis składni szablonu trasy, zobacz Dokumentacja 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 . Conventions:

  • 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

ASP.NET Core aplikacje mogą mieszać użycie konwencjonalnego routingu i routingu atrybutów. Zazwyczaj używasz konwencjonalnych tras dla kontrolerów, które udostępniają strony HTML przeglądarkom, oraz routingu opartego na atrybutach dla kontrolerów obsługujących interfejsy REST API.

Akcje są tradycyjnie kierowane lub kierowane przez atrybuty. Gdy umieszczasz trasę na kontrolerze lub akcji, staje się ona routowana przez atrybut. Nie można uzyskać dostępu do akcji, które definiują ścieżki atrybutowe 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 Encoded
/ %2F
+

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

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ścią url zmiennej jest ciąg /UrlGeneration/Destinationścieżki adresu URL . Routing tworzy tę ścieżkę adresu URL, łącząc:

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

Gdy używasz Url.Action, środowisko uruchomieniowe udostępnia bieżące wartości trasy dla controller i action:

  • Wartości 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.
  • Nie ma już wartości dla d, a generowanie adresu URL kończy się błędem.
  • Musisz określić żądane wartości c i d , 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ą przekazywane w części zapytania URL.

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 jednej z następujących opcji:

  • 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

IHtmlHelper udostępnia metody HtmlHelperHtml.BeginForm i Html.ActionLink do generowania odpowiednio elementów <form> i <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.

W widokach obiekt IUrlHelper jest dostępny za pośrednictwem właściwości Url do dowolnego doraźnego generowania adresów URL, które nie jest objęte opisanymi wcześniej metodami.

Generowanie adresu URL w wynikach akcji

W poprzednich przykładach pokazano, jak używać 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") wygeneruje ścieżkę / adresu URL przy użyciu default trasy, ale dlaczego? Możesz odgadnąć, że wartości { controller = Home, action = Index } trasy są 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.

Areas

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 pochodzi z domyślnej wartości dla area. Trasa utworzona przez MapAreaControllerRoute jest równoważna następującemu kodowi:

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ę przy użyciu zarówno wartości domyślnej, jak i ograniczenia dla area , używając podanej nazwy obszaru, w tym przypadku Blog. Wartość domyślna gwarantuje, że trasa zawsze generuje wartość { area = Blog, ... }, a ograniczenie wymaga wartości { area = Blog, ... } do generowania adresów URL.

Routing konwencjonalny jest zależny od kolejności. Ogólnie rzecz biorąc, umieść trasy z obszarami wcześniej, ponieważ są one 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 należą do żadnego obszaru i nie są dopasowywane, gdy routing dostarcza wartość trasy area. 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}");
        }
    }
}

Aby uzyskać kompletność, przestrzeń nazw każdego kontrolera jest wyświetlana tutaj. 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. Ustaw poziom dziennika w środowisku Development w: appsettings.Development.json

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

kontrolery ASP.NET Core używają middleware, 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 Zwykle kod jest podobny do poniższego przykładu podczas 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. Aby uzyskać szczegółowy opis składni szablonu trasy, zobacz Dokumentacja 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() { ... }
}

Przy użyciu powyższej definicji kontrolera i szablonu trasy akcja HomeController.Index jest uruchamiana dla następujących ścieżek URL:

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

Ścieżka URL / używa szablonu trasy z domyślnym kontrolerem Home i akcją Index. Ścieżka adresu URL /Home używa domyślnej Index akcji szablonu trasy.

Metoda MapDefaultControllerRoutewygody:

endpoints.MapDefaultControllerRoute();

Replaces:

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

Important

Konfigurowanie routingu odbywa się przy użyciu oprogramowania pośredniczącego UseRouting, MapControllerRoute i MapAreaControllerRoute. Aby użyć kontrolerów:

Routing konwencjonalny

Używaj konwencjonalnego routingu z kontrolerami i widokami. Trasa default przebiega następująco:

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

Powyższy kod 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 odpowiada encji 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.

Korzystając z konwencjonalnego routingu z trasą domyślną, nie musisz tworzyć 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.

Warning

Szablon trasy definiuje id jako opcjonalny. 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:

  • Wiązanie modelu ustawia id na 0.
  • 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 wersji ASP.NET Core 3.0 lub nowszej:

  • 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ą. Jest to dedykowana trasa konwencjonalna, ponieważ:

Ponieważ szablon trasy "blog/{*article}" nie zawiera parametrów controller i action:

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

W powyższym przykładzie:

  • Trasa blog ma wyższy priorytet dla dopasowań niż default trasa, ponieważ dodajesz ją jako pierwszą.
  • Jest to przykład routingu w stylu Slug , w którym zazwyczaj jest używana nazwa artykułu jako część adresu URL.

Warning

W wersji ASP.NET Core 3.0 lub nowszej routing nie jest:

  • 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

Routing konwencjonalny odpowiada tylko kombinacji akcji i kontrolera zdefiniowanego przez aplikację. Takie podejście upraszcza przypadki nakładania się konwencjonalnych tras. Podczas dodawania tras za pomocą MapControllerRoute, MapDefaultControllerRoute i MapAreaControllerRoute punkty końcowe automatycznie otrzymują wartość kolejności na podstawie kolejności wywoływania tych metod. Dopasowania z trasy wyświetlanej wcześniej na liście mają wyższy priorytet. Routing konwencjonalny zależy od kolejności. Ogólnie rzecz biorąc, umieść trasy z obszarami wcześniej, ponieważ są bardziej szczegółowe niż trasy bez obszaru. Dedykowane trasy konwencjonalne z parametrami trasy przechwytującymi wszystko, takimi jak {*article}, mogą sprawić, że trasa stanie się zbyt zachłanna. Żarłoczna ścieżka dopasowuje adresy URL, które powinny być obsługiwane przez inne ścieżki. Umieść chciwe trasy później w tabeli tras, aby zapobiec chciwym dopasowaniom.

Warning

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

  • Trasa ogólna, 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 na przykład przypadki, w których wystąpiła ta usterka.

Poprawka opcjonalna tego błędu jest zawarta w .NET Core 3.1.301 lub nowszym SDK. 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 pasują do siebie w ramach routingu, routing musi wykonać jedną z poniższych czynności:

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

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łasza wyjątek AmbiguousMatchException i wyświetla listę wielu pasujących 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 są zastrzeżonymi nazwami parametrów trasy podczas korzystania z kontrolerów lub stron Razor:

  • action
  • area
  • controller
  • handler
  • page

Używanie page jako parametru trasy w trasowaniu przez atrybuty jest typowym błędem. Ten wybór powoduje 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);
    }
}

Generowanie adresów URL używa tych specjalnych nazw parametrów, aby określić, czy operacja generowania adresu URL odwołuje 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

Nie używaj tych słów kluczowych do generowania linków, parametrów powiązanych modelu ani właściwości najwyższego poziomu.

Szablony czasowników HTTP

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

Szablony tras

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

Routing atrybutów z atrybutami 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, więc id dołącza go do szablonu "api/[controller]" 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}i tak dalej.
    [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. Ten projekt oznacza, że wiele operacji, takich jak GET i POST w tym samym zasobie logicznym, używa tego samego adresu URL. Routing według atrybutu zapewnia poziom kontroli potrzebny, aby starannie zaprojektować publiczne punkty końcowe 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);
    }
}

Użyj nazw tras, aby wygenerować adres 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 routing atrybutów był mniej powtarzalny, połącz atrybuty trasy na kontrolerze z atrybutami trasy w poszczególnych akcjach. Szablony tras zdefiniowane na kontrolerze są dołączane do szablonów tras w akcjach. W przypadku umieszczenia atrybutu trasy na kontrolerze 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 stosowanymi 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();
    }
}

W poniższej tabeli wyjaśniono atrybuty [Route] w powyższym kodzie.

Attribute Łączy się z [Route("Home")] Definiuje wzorzec trasy
[Route("")] Yes "Home"
[Route("Index")] Yes "Home/Index"
[Route("/")] Nie ""
[Route("About")] Yes "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 przy użyciu powyższego kodu 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 słabego projektu routingu. Ilustruje właściwość Order.
  • Właściwość Order rozwiązuje tylko niejednoznaczność. Nie można dopasować tego szablonu. Lepiej jest usunąć [Route("Home")] szablon.

Aby uzyskać informacje o kolejności tras w Razor Pages, zobacz Razor Konwencje tras i aplikacji w Pages: kolejność tras.

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 atrybutowe obsługują zastępowanie tokenów przez ujęcie tokenu w nawiasy kwadratowe ([, ]). Tokeny [action], [area]i [controller] są zastępowane wartościami nazwy akcji, nazwy obszaru i nazwy kontrolera z akcji, w której definiujesz 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}

Zamiana 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 tym wątku dyskusji na GitHubie, jeśli chcesz zobaczyć komentarze kodu w swoim języku ojczystym.

Możesz również połączyć trasy atrybutów z dziedziczeniem. Ta kombinacja jest potężna, gdy używasz zastępowania tokenów. 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 funkcji przekształcania 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:

  • Zastosuj transformator parametru do wszystkich tras atrybutów w aplikacji.
  • Dostosowuje wartości tokenów w trasach atrybutów, gdy są one zastępowane.
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()));
    });
}

Definicję terminu „slug” znajdziesz w dokumentacji MDN Web Docs dotyczącej terminu „slug”.

Warning

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.

Aby uzyskać szczegółowy opis składni szablonu trasy, zobacz Dokumentacja 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 . Conventions:

  • 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

ASP.NET Core aplikacje mogą mieszać użycie konwencjonalnego routingu i routingu atrybutów. Zazwyczaj używasz konwencjonalnych tras dla kontrolerów, które udostępniają strony HTML przeglądarkom, oraz routingu opartego na atrybutach dla kontrolerów obsługujących interfejsy REST 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ścią url zmiennej jest ciąg /UrlGeneration/Destinationścieżki adresu URL . Routing tworzy tę ścieżkę adresu URL, łącząc:

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

Gdy używasz Url.Action, środowisko uruchomieniowe udostępnia bieżące wartości trasy dla controller i action:

  • Wartości 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.
  • Nie ma już wartości dla d, a generowanie adresu URL kończy się błędem.
  • Musisz określić żądane wartości c i d , 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ą przekazywane w części zapytania URL.

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 jednej z następujących opcji:

  • 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

IHtmlHelper udostępnia metody HtmlHelperHtml.BeginForm i Html.ActionLink do generowania odpowiednio elementów <form> i <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.

W widokach obiekt IUrlHelper jest dostępny za pośrednictwem właściwości Url do dowolnego doraźnego generowania adresów URL, które nie jest objęte opisanymi wcześniej metodami.

Generowanie adresu URL w wynikach akcji

W poprzednich przykładach pokazano, jak używać 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") wygeneruje ścieżkę / adresu URL przy użyciu default trasy, ale dlaczego? Możesz odgadnąć, że wartości { controller = Home, action = Index } trasy są 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.

Areas

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 pochodzi z domyślnej wartości dla area. Trasa utworzona przez MapAreaControllerRoute jest równoważna następującemu kodowi:

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ę przy użyciu zarówno wartości domyślnej, jak i ograniczenia dla area , używając podanej nazwy obszaru, w tym przypadku Blog. Wartość domyślna gwarantuje, że trasa zawsze generuje wartość { area = Blog, ... }, a ograniczenie wymaga wartości { area = Blog, ... } do generowania adresów URL.

Routing konwencjonalny jest zależny od kolejności. Ogólnie rzecz biorąc, umieść trasy z obszarami wcześniej, ponieważ są one 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 należą do żadnego obszaru i nie są dopasowywane, gdy routing dostarcza wartość trasy area. 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}");
        }
    }
}

Aby uzyskać kompletność, przestrzeń nazw każdego kontrolera jest wyświetlana tutaj. 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. Ustaw poziom dziennika w środowisku Development w: appsettings.Development.json

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