Udostępnij za pośrednictwem


Routing do akcji kontrolera na platformie ASP.NET Core

Ryan Nowak, Kirk Larkin i Rick Anderson

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ostrzeżenie

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

Ważne

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

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

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

  • Są definiowane podczas uruchamiania w Program.cs atrybutach lub w.
  • 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 sprawia, że jest kierowany atrybutem. Aby uzyskać więcej informacji, zobacz Routing mieszany .

Ten dokument:

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

Konfigurowanie konwencjonalnej trasy

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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

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

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

app.UseRouting();

app.UseAuthorization();

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

app.Run();

MapControllerRoute służy do tworzenia pojedynczej trasy. Pojedyncza trasa nosi nazwę default route. Większość aplikacji z kontrolerami i widokami używa szablonu trasy podobnego do default trasy. 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 powoduje dopasowanie, jeśli aplikacja ma kontroler o nazwie ProductsController i Details akcję:

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

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

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

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

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

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

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

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

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

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

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

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

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

Metoda MapDefaultControllerRoutewygody:

app.MapDefaultControllerRoute();

Zastępuje:

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

Ważne

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

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

Routing konwencjonalny

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

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

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

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

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

  • /Products/List mapuje na ProductsController.List akcję.
  • /Blog/Article/17 mapuje na BlogController.Article i zazwyczaj model wiąże id parametr z wartością 17.

To mapowanie:

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

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

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

Ostrzeżenie

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

  • id parametr jest ustawiany na 0 wartość według powiązania modelu.
  • W bazie danych pasującej id == 0nie można odnaleźć jednostki .

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

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

  • Obsługuje podstawowy i opisowy schemat routingu.
  • Jest przydatnym punktem wyjścia dla aplikacji opartych na interfejsie użytkownika.
  • Jest jedynym szablonem trasy wymaganym dla wielu aplikacji internetowych interfejsu użytkownika. 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ść zamówienia do swoich punktów końcowych na podstawie kolejności, w której są wywoływane.

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

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

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

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

Wiele konwencjonalnych tras

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

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

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

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

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

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

Powyższy przykład:

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

Ostrzeżenie

W ASP.NET Core routing nie:

  • 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.
  • Zapewnianie gwarancji dotyczących kolejności wykonywania rozszerzalności, takich jak IRouteConstraint lub IActionConstraint.

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

Tradycyjna kolejność routingu

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

Ostrzeżenie

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

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

Zobacz Usterki usługi GitHub 18677 i 16579 , na przykład przypadki, w których wystąpiła ta usterka.

Poprawka zgody na tę usterkę jest zawarta w zestawie .NET Core 3.1.301 SDK i nowszych wersjach. Poniższy kod ustawia przełącznik wewnętrzny, który naprawia tę usterkę:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Rozwiązywanie niejednoznacznych akcji

Gdy dwa punkty końcowe są zgodne z routingiem, routing musi wykonać jedną z następujących czynności:

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

Na przykład:

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

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

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

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

Jest to typowy wzorzec dla kontrolerów MVC:

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

Aby rozwiązać 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

[HttpPost]Element HttpPostAttribute, jest dostarczany do routingu, aby można było wybrać go 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 Badanie dwóch metod akcji Edytuj.

Jeśli routing nie może wybrać najlepszego kandydata, AmbiguousMatchException zostanie zgłoszony element z listą wielu pasowanych punktów końcowych.

Konwencjonalne nazwy tras

Ciągi i "default" w następujących przykładach "blog" 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. Nazwa trasy i nazwa punktu końcowego terminów:

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

Routing atrybutów używa zestawu atrybutów do mapowania akcji 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 jest wywoływana do MapControllers mapowania atrybutów kierowanych kontrolerów.

W poniższym przykładzie:

  • HomeController pasuje do zestawu adresów URL podobnych do domyślnej konwencjonalnej trasy {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 routing atrybutów zezwala i wymaga dokładnej kontroli, które szablony tras mają zastosowanie do każdej akcji.

W przypadku routingu atrybutów nazwy kontrolera i akcji nie odgrywają żadnej roli, w której akcja jest dopasowywana, chyba że jest używana 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 zamiany tokenu dla action elementów 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 Index szablony metod muszą poprzedzać / szablony tras lub ~/ do nich. Szablony tras stosowane do akcji rozpoczynającej się / od lub ~/ nie są łączone z szablonami tras zastosowanymi do kontrolera.

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

Nazwy routingu zarezerwowanego

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

  • action
  • area
  • controller
  • handler
  • page

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

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

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

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

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

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

Szablony czasowników HTTP

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

Szablony tras

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

Routing atrybutów 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);
    }
}

Powyższy kod:

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

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

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

REST Interfejsy API powinny używać routingu atrybutów do modelowania funkcjonalności aplikacji jako zestawu zasobów, w których operacje są reprezentowane przez czasowniki HTTP. Oznacza to, że wiele operacji, na przykład GET i POST w tym samym zasobie logicznym, używa tego samego adresu URL. Routing atrybutów zapewnia poziom kontroli, który jest potrzebny do dokładnego zaprojektowania publicznego układu punktu końcowego interfejsu 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 przy użyciu ścieżki /products2adresu URL .

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]programu , zobacz Atrybut ApiController.

Nazwa trasy

Poniższy kod definiuje nazwę trasy :Products_List

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

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

  • Nie ma wpływu na zachowanie dopasowywania adresów URL routingu.
  • 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ść dokładnego określenia interfejsów API ma zalety, takie jak umożliwienie /products i /products/5 wysłanie ich do różnych akcji.

Łączenie tras atrybutów

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

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

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

W powyższym przykładzie:

  • Ścieżka /products adresu URL może być zgodna 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 rozpoczynającej się / od lub ~/ nie są łączone z szablonami tras zastosowanymi do kontrolera. Poniższy przykład pasuje do zestawu ścieżek URL podobnych do trasy domyślnej.

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

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

W poniższej tabeli opisano [Route] atrybuty w poprzednim kodzie:

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

Kolejność tras atrybutów

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

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

Na przykład trasa atrybutu, na blog/search/{topic} przykład, jest bardziej specyficzna niż trasa atrybutu, na przykład 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 udostępnione atrybuty Order trasy obejmują . Trasy są przetwarzane zgodnie z rosnącą właściwością Order . Domyślna kolejność to 0. Ustawianie trasy przy użyciu Order = -1 przebiegów przed trasami, które nie ustawiają kolejności. Ustawianie trasy przy użyciu Order = 1 przebiegów po domyślnym porządkowaniu tras.

Unikaj w zależności od Order. Jeśli miejsce w adresie URL aplikacji wymaga jawnych wartości zamówień w celu 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 /hometrasy:

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

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

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

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

AmbiguousMatchException: The request matched multiple endpoints. Matches:

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

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

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

W poprzednim kodzie /home uruchamia HomeController.Index punkt końcowy. Aby uzyskać dostęp do MyDemoController.MyIndexelementu , żądanie /home/MyIndex. Uwaga:

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

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

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

Zamiana tokenu w szablonach tras [kontroler], [action], [area]

Dla wygody trasy atrybutów obsługują zamianę tokenu przez dołączenie 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 zdefiniowano trasę:

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


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

Powyższy kod:

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

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

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

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

Jeśli czytasz to w języku innym niż angielski, daj nam znać w tym problemie z dyskusją w usłudze GitHub, jeśli chcesz zobaczyć komentarze kodu w języku natywnym.

Trasy atrybutów można również łączyć z dziedziczeniem. Jest to zaawansowane połączenie z zastąpieniem tokenu. Zamiana tokenu ma również zastosowanie do nazw tras zdefiniowanych przez trasy 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);
    }
}

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

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

Zamiana 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 funkcja przekształcania parametrów niestandardowych 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();
    }
}

Jest RouteTokenTransformerConvention to konwencja modelu aplikacji, która:

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

Poprzednia metoda odpowiada /subscription-management/list-allmetodzie ListAll .

Element RouteTokenTransformerConvention jest zarejestrowany jako opcja:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

app.UseRouting();

app.UseAuthorization();

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

app.Run();

Aby uzyskać definicję usługi Slug, zobacz dokumentację internetową usługi MDN w usłudze Slug .

Ostrzeżenie

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

Wiele tras atrybutów

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 wiele IActionConstraint atrybutów trasy implementujących jest umieszczanych w akcji:

  • 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ę przydatne i zaawansowane, lepiej zachować podstawową i dobrze zdefiniowaną przestrzeń adresów URL aplikacji. Użyj wielu tras w akcjach tylko w razie potrzeby, aby obsługiwać istniejących klientów.

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

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

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

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

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

Niestandardowe atrybuty trasy przy użyciu elementu IRouteTemplateProvider

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

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

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

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

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

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

Dostosowywanie tras atrybutów za pomocą modelu aplikacji

Model aplikacji:

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

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

  • Można zapisać 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 namespace zastosowanie konwencji do kontrolerów, które są kierowane atrybutami:

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

Powyższy kod:

  • 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żna NamespaceRoutingConvention go również zastosować jako atrybut na kontrolerze:

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

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

Routing mieszany: routing atrybutów a routing konwencjonalny

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

Akcje są zwykle kierowane lub kierowane atrybuty. Umieszczenie trasy na kontrolerze lub akcji sprawia, że atrybut jest kierowany. 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 aparatu routingu.

Routing z znakami specjalnymi

Routing ze znakami specjalnymi może prowadzić do nieoczekiwanych wyników. Rozważmy na przykład kontroler z następującą metodą akcji:

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

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

    return todoItem.Name;
}

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

ASCII Encoded
/ %2F
+

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

Generowanie adresów URL i wartości otoczenia

Aplikacje mogą używać funkcji generowania adresów URL routingu do generowania linków adresów URL do akcji. Generowanie adresów URL eliminuje stałe adresy URL kodowania , dzięki czemu kod jest bardziej niezawodny i możliwy 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. Wystąpienie IUrlHelper klasy jest dostępne za pośrednictwem Url właściwości w kontrolerach, widokach i składnikach widoku.

W poniższym przykładzie IUrlHelper interfejs jest używany za pośrednictwem Controller.Url właściwości w celu wygenerowania adresu URL do innej akcji.

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

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

Jeśli aplikacja używa domyślnej konwencjonalnej trasy, wartość url zmiennej to ciąg /UrlGeneration/Destinationścieżki adresu URL . Ta ścieżka adresu URL jest tworzona przez routing przez połączenie:

  • 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ść podstawioną przez dopasowanie nazw wartościami i wartościami otoczenia. Parametr trasy, który nie ma wartości, może:

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

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.Action założenie, że routing konwencjonalny. Generowanie adresów URL działa podobnie z routingiem atrybutów, chociaż koncepcje są różne. Z konwencjonalnym routingiem:

  • 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 i wszystkie powiązane przeciążenia są przeznaczone do generowania docelowego punktu końcowego przez określenie nazwy kontrolera i nazwy akcji.

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

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

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

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

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

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

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

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

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

Kilka przeciążeń adresu URL.Action wykonuje obiekt wartości trasy, aby podać wartości parametrów trasy innych niż controller i 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<> element lub POCO).

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

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

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

Poniższy kod generuje bezwzględny adres URL:

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

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

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

Generowanie adresów URL według trasy

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

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

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

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

<h1>Test Links</h1>

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

Generowanie adresów URL w kodzie HTML i Razor

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

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

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

Generowanie adresu URL w wynikach akcji

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

Klasy ControllerBase i Controller zapewniają wygodne metody dla wyników akcji odwołujących się do innej akcji. 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);
}

Akcja powoduje, że metody fabryki, takie jak RedirectToAction i CreatedAtAction , są zgodne ze wzorcem podobnym do metod w systemie IUrlHelper.

Szczególny przypadek dla dedykowanych tras konwencjonalnych

Routing konwencjonalny może używać specjalnego rodzaju definicji trasy nazywanej dedykowaną trasą konwencjonalną. 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że się okazać, że wartości { controller = Home, action = Index } trasy będą wystarczające, aby wygenerować adres URL przy użyciu metody blog, a wynikiem będzie /blog?action=Index&controller=Home.

Dedykowane trasy konwencjonalne polegają na specjalnym zachowaniu wartości domyślnych, które nie mają odpowiedniego parametru trasy, który uniemożliwia zbyt chciwość trasy generowania adresów URL. W takim przypadku wartości domyślne to { controller = Blog, action = Article }, ani ani controller nie action są wyświetlane jako parametr trasy. Gdy routing wykonuje generowanie adresów URL, podane wartości muszą być zgodne z wartościami domyślnymi. Generowanie adresu URL przy użyciu metody kończy się niepowodzeniem blog , ponieważ wartości { controller = Home, action = Index } nie są zgodne { controller = Blog, action = Article }z . Następnie routing wraca do próby default, co powiedzie się.

Obszary

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

  • Routing 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 umożliwia skonfigurowanie wzorca MVC do używania domyślnej trasy konwencjonalnej i area trasy dla nazwanego area Blogelementu :

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 jest wywoływany w MapAreaControllerRoute celu utworzenia elementu "blog_route". Drugi parametr , "Blog"to nazwa obszaru.

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

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

MapAreaControllerRoute Tworzy trasę przy użyciu zarówno wartości domyślnej, jak i ograniczenia dla area używania podanej nazwy obszaru, w tym przypadku Blog. Wartość domyślna gwarantuje, że trasa zawsze generuje { area = Blog, ... }wartość , ograniczenie wymaga wartości { area = Blog, ... } generowania adresu URL.

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

Korzystając z powyższego przykładu, wartości { area = Blog, controller = Users, action = AddUser } tras są zgodne z następującą akcją:

using Microsoft.AspNetCore.Mvc;

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

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

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

using Microsoft.AspNetCore.Mvc;

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

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

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

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

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

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

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

Dwa pierwsze kontrolery są członkami obszarów i są zgodne tylko wtedy, gdy ich nazwa obszaru jest podana area przez wartość trasy. Trzeci kontroler nie jest członkiem żadnego obszaru i może być zgodny tylko wtedy, gdy żadna wartość area nie jest dostarczana przez routing.

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:Microsoft Debug. W środowisku deweloperów ustaw poziom dziennika w pliku appsettings.Development.json:

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

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

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

Akcje są tradycyjnie kierowane lub kierowane przez atrybuty. Umieszczenie trasy na kontrolerze lub akcji sprawia, że jest kierowany atrybutem. Aby uzyskać więcej informacji, zobacz Routing mieszany .

Ten dokument:

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

Konfigurowanie konwencjonalnej trasy

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

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

Wewnątrz wywołania metody UseEndpointsMapControllerRoute jest używana do tworzenia pojedynczej trasy. Pojedyncza trasa nosi nazwę default route. Większość aplikacji z kontrolerami i widokami używa szablonu trasy podobnego do default trasy. 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 powoduje dopasowanie, jeśli aplikacja ma kontroler o nazwie ProductsController i Details akcję:

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

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

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

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

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

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

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

  • Pasuje do ścieżki /adresu URL .

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

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

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

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

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

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

Metoda MapDefaultControllerRoutewygody:

endpoints.MapDefaultControllerRoute();

Zastępuje:

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

Ważne

Routing jest konfigurowany przy użyciu oprogramowania pośredniczącego UseRouting, MapControllerRoutei MapAreaControllerRoute . Aby użyć kontrolerów:

Routing konwencjonalny

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

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

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

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

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

  • /Products/List mapuje na ProductsController.List akcję.
  • /Blog/Article/17 mapuje na BlogController.Article i zazwyczaj model wiąże id parametr z wartością 17.

To mapowanie:

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

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

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

Ostrzeżenie

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

  • id parametr jest ustawiany na 0 wartość według powiązania modelu.
  • W bazie danych pasującej id == 0nie można odnaleźć jednostki .

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

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

  • Obsługuje podstawowy i opisowy schemat routingu.
  • Jest przydatnym punktem wyjścia dla aplikacji opartych na interfejsie użytkownika.
  • Jest jedynym szablonem trasy wymaganym dla wielu aplikacji internetowych interfejsu użytkownika. 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ść zamówienia do swoich punktów końcowych na podstawie kolejności, w której są wywoływane.

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

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

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

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

Wiele konwencjonalnych tras

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

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

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

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

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

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

Powyższy przykład:

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

Ostrzeżenie

W programie ASP.NET Core 3.0 lub nowszym 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.
  • Zapewnianie gwarancji dotyczących kolejności wykonywania rozszerzalności, takich jak IRouteConstraint lub IActionConstraint.

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

Tradycyjna kolejność routingu

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

Ostrzeżenie

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

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

Zobacz Usterki usługi GitHub 18677 i 16579 , na przykład przypadki, w których wystąpiła ta usterka.

Poprawka zgody na tę usterkę jest zawarta w zestawie .NET Core 3.1.301 SDK i nowszych wersjach. Poniższy kod ustawia przełącznik wewnętrzny, który naprawia tę usterkę:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Rozwiązywanie niejednoznacznych akcji

Gdy dwa punkty końcowe są zgodne z routingiem, routing musi wykonać jedną z następujących czynności:

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

Na przykład:

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

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

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

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

Jest to typowy wzorzec dla kontrolerów MVC:

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

Aby rozwiązać 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

[HttpPost]Element HttpPostAttribute, jest dostarczany do routingu, aby można było wybrać go 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 Badanie dwóch metod akcji Edytuj.

Jeśli routing nie może wybrać najlepszego kandydata, AmbiguousMatchException zostanie zgłoszony element z listą wielu pasowanych punktów końcowych.

Konwencjonalne nazwy tras

Ciągi i "default" w następujących przykładach "blog" 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. Nazwa trasy i nazwa punktu końcowego terminów:

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

Routing atrybutów używa zestawu atrybutów do mapowania akcji 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 jest wywoływana wewnątrzUseEndpoints, MapControllers aby mapować atrybuty kierowane kontrolery.

W poniższym przykładzie:

  • HomeController pasuje do zestawu adresów URL podobnych do domyślnej konwencjonalnej trasy {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 routing atrybutów zezwala i wymaga dokładnej kontroli, które szablony tras mają zastosowanie do każdej akcji.

W przypadku routingu atrybutów nazwy kontrolera i akcji nie odgrywają żadnej roli, w której akcja jest dopasowywana, chyba że jest używana 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 zamiany tokenu dla action elementów 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 Index szablony metod muszą poprzedzać / szablony tras lub ~/ do nich. Szablony tras stosowane do akcji rozpoczynającej się / od lub ~/ nie są łączone z szablonami tras zastosowanymi do kontrolera.

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

Nazwy routingu zarezerwowanego

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

  • action
  • area
  • controller
  • handler
  • page

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

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

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

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

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

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

Szablony czasowników HTTP

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

Szablony tras

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

Routing atrybutów 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);
    }
}

Powyższy kod:

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

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

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

REST Interfejsy API powinny używać routingu atrybutów do modelowania funkcjonalności aplikacji jako zestawu zasobów, w których operacje są reprezentowane przez czasowniki HTTP. Oznacza to, że wiele operacji, na przykład GET i POST w tym samym zasobie logicznym, używa tego samego adresu URL. Routing atrybutów zapewnia poziom kontroli, który jest potrzebny do dokładnego zaprojektowania publicznego układu punktu końcowego interfejsu 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 przy użyciu ścieżki /products2adresu URL .

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]programu , zobacz Atrybut ApiController.

Nazwa trasy

Poniższy kod definiuje nazwę trasy :Products_List

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

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

  • Nie ma wpływu na zachowanie dopasowywania adresów URL routingu.
  • 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ść dokładnego określenia interfejsów API ma zalety, takie jak umożliwienie /products i /products/5 wysłanie ich do różnych akcji.

Łączenie tras atrybutów

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

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

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

W powyższym przykładzie:

  • Ścieżka /products adresu URL może być zgodna 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 rozpoczynającej się / od lub ~/ nie są łączone z szablonami tras zastosowanymi do kontrolera. Poniższy przykład pasuje do zestawu ścieżek URL podobnych do trasy domyślnej.

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

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

W poniższej tabeli opisano [Route] atrybuty w poprzednim kodzie:

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

Kolejność tras atrybutów

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

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

Na przykład trasa atrybutu, na blog/search/{topic} przykład, jest bardziej specyficzna niż trasa atrybutu, na przykład 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 udostępnione atrybuty Order trasy obejmują . Trasy są przetwarzane zgodnie z rosnącą właściwością Order . Domyślna kolejność to 0. Ustawianie trasy przy użyciu Order = -1 przebiegów przed trasami, które nie ustawiają kolejności. Ustawianie trasy przy użyciu Order = 1 przebiegów po domyślnym porządkowaniu tras.

Unikaj w zależności od Order. Jeśli miejsce w adresie URL aplikacji wymaga jawnych wartości zamówień w celu 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 /hometrasy:

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

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

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

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

AmbiguousMatchException: The request matched multiple endpoints. Matches:

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

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

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

W poprzednim kodzie /home uruchamia HomeController.Index punkt końcowy. Aby uzyskać dostęp do MyDemoController.MyIndexelementu , żądanie /home/MyIndex. Uwaga:

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

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

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

Zamiana tokenu w szablonach tras [kontroler], [action], [area]

Dla wygody trasy atrybutów obsługują zamianę tokenu przez dołączenie 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 zdefiniowano trasę:

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


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

Powyższy kod:

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

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

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

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

Jeśli czytasz to w języku innym niż angielski, daj nam znać w tym problemie z dyskusją w usłudze GitHub, jeśli chcesz zobaczyć komentarze kodu w języku natywnym.

Trasy atrybutów można również łączyć z dziedziczeniem. Jest to zaawansowane połączenie z zastąpieniem tokenu. Zamiana tokenu ma również zastosowanie do nazw tras zdefiniowanych przez trasy 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);
    }
}

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

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

Zamiana 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 funkcja przekształcania parametrów niestandardowych 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();
    }
}

Jest RouteTokenTransformerConvention to konwencja modelu aplikacji, która:

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

Poprzednia metoda odpowiada /subscription-management/list-allmetodzie ListAll .

Element RouteTokenTransformerConvention jest zarejestrowany jako opcja w ConfigureServicespliku .

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

Aby uzyskać definicję usługi Slug, zobacz dokumentację internetową usługi MDN w usłudze Slug .

Ostrzeżenie

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

Wiele tras atrybutów

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 wiele IActionConstraint atrybutów trasy implementujących jest umieszczanych w akcji:

  • 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ę przydatne i zaawansowane, lepiej zachować podstawową i dobrze zdefiniowaną przestrzeń adresów URL aplikacji. Użyj wielu tras w akcjach tylko w razie potrzeby, aby obsługiwać istniejących klientów.

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

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

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

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

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

Niestandardowe atrybuty trasy przy użyciu elementu IRouteTemplateProvider

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

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

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

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

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

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

Dostosowywanie tras atrybutów za pomocą modelu aplikacji

Model aplikacji:

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

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

  • Można zapisać 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 namespace zastosowanie konwencji do kontrolerów, które są kierowane atrybutami:

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

Powyższy kod:

  • 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żna NamespaceRoutingConvention go również zastosować jako atrybut na kontrolerze:

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

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

Routing mieszany: routing atrybutów a routing konwencjonalny

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

Akcje są zwykle kierowane lub kierowane atrybuty. Umieszczenie trasy na kontrolerze lub akcji sprawia, że atrybut jest kierowany. 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 aparatu routingu.

Generowanie adresów URL i wartości otoczenia

Aplikacje mogą używać funkcji generowania adresów URL routingu do generowania linków adresów URL do akcji. Generowanie adresów URL eliminuje trwałe adresy URL, dzięki czemu kod jest bardziej niezawodny i możliwy 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. Wystąpienie IUrlHelper klasy jest dostępne za pośrednictwem Url właściwości w kontrolerach, widokach i składnikach widoku.

W poniższym przykładzie IUrlHelper interfejs jest używany za pośrednictwem Controller.Url właściwości w celu wygenerowania adresu URL do innej akcji.

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

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

Jeśli aplikacja używa domyślnej konwencjonalnej trasy, wartość url zmiennej to ciąg /UrlGeneration/Destinationścieżki adresu URL . Ta ścieżka adresu URL jest tworzona przez routing przez połączenie:

  • 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ść podstawioną przez dopasowanie nazw wartościami i wartościami otoczenia. Parametr trasy, który nie ma wartości, może:

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

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.Action założenie, że routing konwencjonalny. Generowanie adresów URL działa podobnie z routingiem atrybutów, chociaż koncepcje są różne. Z konwencjonalnym routingiem:

  • 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 i wszystkie powiązane przeciążenia są przeznaczone do generowania docelowego punktu końcowego przez określenie nazwy kontrolera i nazwy akcji.

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

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

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

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

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

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

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

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

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

Kilka przeciążeń adresu URL.Action wykonuje obiekt wartości trasy, aby podać wartości parametrów trasy innych niż controller i 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<> element lub POCO).

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

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

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

Poniższy kod generuje bezwzględny adres URL:

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

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

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

Generowanie adresów URL według trasy

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

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

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

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

<h1>Test Links</h1>

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

Generowanie adresów URL w kodzie HTML i Razor

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

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

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

Generowanie adresu URL w wynikach akcji

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

Klasy ControllerBase i Controller zapewniają wygodne metody dla wyników akcji odwołujących się do innej akcji. 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);
}

Akcja powoduje, że metody fabryki, takie jak RedirectToAction i CreatedAtAction , są zgodne ze wzorcem podobnym do metod w systemie IUrlHelper.

Szczególny przypadek dla dedykowanych tras konwencjonalnych

Routing konwencjonalny może używać specjalnego rodzaju definicji trasy nazywanej dedykowaną trasą konwencjonalną. 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że się okazać, że wartości { controller = Home, action = Index } trasy będą wystarczające, aby wygenerować adres URL przy użyciu metody blog, a wynikiem będzie /blog?action=Index&controller=Home.

Dedykowane trasy konwencjonalne polegają na specjalnym zachowaniu wartości domyślnych, które nie mają odpowiedniego parametru trasy, który uniemożliwia zbyt chciwość trasy generowania adresów URL. W takim przypadku wartości domyślne to { controller = Blog, action = Article }, ani ani controller nie action są wyświetlane jako parametr trasy. Gdy routing wykonuje generowanie adresów URL, podane wartości muszą być zgodne z wartościami domyślnymi. Generowanie adresu URL przy użyciu metody kończy się niepowodzeniem blog , ponieważ wartości { controller = Home, action = Index } nie są zgodne { controller = Blog, action = Article }z . Następnie routing wraca do próby default, co powiedzie się.

Obszary

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

  • Routing 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 umożliwia skonfigurowanie wzorca MVC do używania domyślnej trasy konwencjonalnej i area trasy dla nazwanego area Blogelementu :

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

W poprzednim kodzie jest wywoływany w MapAreaControllerRoute celu utworzenia elementu "blog_route". Drugi parametr , "Blog"to nazwa obszaru.

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

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

MapAreaControllerRoute Tworzy trasę przy użyciu zarówno wartości domyślnej, jak i ograniczenia dla area używania podanej nazwy obszaru, w tym przypadku Blog. Wartość domyślna gwarantuje, że trasa zawsze generuje { area = Blog, ... }wartość , ograniczenie wymaga wartości { area = Blog, ... } generowania adresu URL.

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

Korzystając z powyższego przykładu, wartości { area = Blog, controller = Users, action = AddUser } tras są zgodne z następującą akcją:

using Microsoft.AspNetCore.Mvc;

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

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

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

using Microsoft.AspNetCore.Mvc;

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

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

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

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

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

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

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

Dwa pierwsze kontrolery są członkami obszarów i są zgodne tylko wtedy, gdy ich nazwa obszaru jest podana area przez wartość trasy. Trzeci kontroler nie jest członkiem żadnego obszaru i może być zgodny tylko wtedy, gdy żadna wartość area nie jest dostarczana przez routing.

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:Microsoft Debug. W środowisku deweloperów ustaw poziom dziennika w pliku appsettings.Development.json:

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