Dela via


Routning till kontrollantåtgärder i ASP.NET Core

Av Ryan Nowak, Kirk Larkin och Rick Anderson

Note

Det här är inte den senaste versionen av den här artikeln. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Warning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Important

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

ASP.NET Core-styrenheter använder routningsmellanprogrammet för att matcha URL:erna för inkommande begäranden och mappa dem till åtgärder. Routningsmallar:

  • Definieras vid start i Program.cs eller i attribut.
  • Beskriv hur URL-sökvägar matchas med åtgärder.
  • Används för att generera URL:er för länkar. De genererade länkarna returneras vanligtvis i svar.

Åtgärder är antingen konventionellt dirigerade eller attributroutade. Att placera en rutt på kontrollern eller åtgärden gör den attributbaserad. Mer information finns i Blandad routning .

Det här dokumentet:

  • Förklarar interaktionerna mellan MVC och routning:
  • Refererar till standarddirigeringssystemet som kallas slutpunktsroutning. Det är möjligt att använda styrenheter med den tidigare versionen av routning i kompatibilitetssyfte. Anvisningar finns i migreringsguiden 2.2-3.0 .

Konfigurera konventionell väg

Mallen ASP.NET Core MVC genererar konventionell routningskod som liknar följande:

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 används för att skapa en enda väg. Den enda vägen heter default route. De flesta appar med kontrollanter och vyer använder en routningsmall som liknar default vägen. REST API:er bör använda attributroutning.

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

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

  • Matchar en URL-sökväg som /Products/Details/5

  • Extraherar vägvärdena { controller = Products, action = Details, id = 5 } genom att tokenisera sökvägen. Extrahering av vägvärden resulterar i en matchning om appen har en styrenhet med namnet ProductsController och en Details åtgärd:

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

    MyDisplayRouteInfo tillhandahålls av Rick.Docs.Samples.RouteInfo NuGet-paketet och visar väginformation.

  • /Products/Details/5 modellen binder värdet id = 5 för att ange parametern id till 5. Mer information finns i Modellbindning .

  • {controller=Home} definierar Home som standard controller.

  • {action=Index} definierar Index som standard action.

  • Tecknet ? i {id?} definierar id som valfritt.

    • Standard- och valfria vägparametrar behöver inte finnas i URL-sökvägen för en matchning. En detaljerad beskrivning av routningsmallssyntaxen finns i Referens för routningsmall .
  • Matchar URL-sökvägen /.

  • Genererar vägvärdena { controller = Home, action = Index }.

Värdena för controller och action använder standardvärdena. id ger inget värde eftersom det inte finns något motsvarande segment i URL-sökvägen. / matchar endast om det finns en HomeController-åtgärd och Index.

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

Med hjälp av föregående kontrollantdefinition och vägmall HomeController.Index körs åtgärden för följande URL-sökvägar:

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

URL-sökvägen / använder standardkontrollanterna Home och Index åtgärden för routningsmallen. URL-sökvägen /Home använder standardåtgärden Index för routningsmallen.

Bekvämlighetsmetoden MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Replaces:

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

Important

Routning konfigureras med hjälp av UseRouting och UseEndpoints middleware. Så här använder du kontroller:

Appar behöver vanligtvis inte anropa UseRouting eller UseEndpoints. WebApplicationBuilder konfigurerar en middleware-pipeline som omsluter den middleware som lagts till i Program.cs med UseRouting och UseEndpoints. Mer information finns i Routning i ASP.NET Core.

Konventionell routning

Konventionell routning används med styrenheter och vyer. Vägen default :

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

Föregående är ett exempel på en konventionell väg. Den kallas för konventionell routning eftersom den upprättar en konvention för URL-sökvägar:

  • Det första sökvägssegmentet, {controller=Home}, mappar till kontrollantnamnet.
  • Det andra segmentet, {action=Index}, mappar till åtgärdsnamnet .
  • Det tredje segmentet {id?} används för en valfri id. ? i {id?} gör det valfritt. id används för att mappa till en modellentitet.

Med hjälp av den här default rutten, URL-sökvägen:

  • /Products/List mappar till åtgärden ProductsController.List .
  • /Blog/Article/17 mappar till BlogController.Article och binder oftast parametern id till 17.

Den här mappningen:

  • Baseras endast på kontrollanten och åtgärdsnamnen.
  • Baseras inte på namnrymder, källfilplatser eller metodparametrar.

Med konventionell routning med standardvägen kan du skapa appen utan att behöva komma med ett nytt URL-mönster för varje åtgärd. För en app med CRUD-formatåtgärder , med konsekvens för URL:er mellan kontrollanter:

  • Hjälper till att förenkla koden.
  • Gör användargränssnittet mer förutsägbart.

Warning

I id föregående kod definieras som valfri av routningsmallen. Åtgärder kan köras utan det valfria ID som anges som en del av URL:en. Vanligtvis när id utelämnas från URL:en:

  • id sätts till 0 av modellbindning.
  • Ingen entitet hittades i databasen som matchar id == 0.

Attributroutning ger detaljerad kontroll för att göra ID obligatoriskt för vissa åtgärder men inte för andra. Enligt konventionen innehåller dokumentationen valfria parametrar som id när de sannolikt kommer att visas i korrekt användning.

De flesta appar bör välja ett grundläggande och beskrivande routningsschema så att URL:er är läsbara och meningsfulla. Standardvägen för konventionell väg {controller=Home}/{action=Index}/{id?}:

  • Stöder ett grundläggande och beskrivande routningsschema.
  • Är en användbar startpunkt för användargränssnittsbaserade appar.
  • Är den enda vägmall som behövs för många webbgränssnittsappar. För större webbgränssnittsappar är en annan väg som använder Områden ofta allt som behövs.

MapControllerRoute och MapAreaRoute :

  • Tilldela automatiskt ett ordervärde till sina slutpunkter baserat på den ordning som de anropas.

Slutpunktsroutning i ASP.NET Core:

  • Har inget begrepp om vägar.
  • Ger inte ordergarantier för körning av utökningsbarhet, alla slutpunkter bearbetas samtidigt.

Aktivera loggning för att se hur de inbyggda routningsimplementeringarna, till exempel Route, matchar begäranden.

Attributroutning förklaras senare i det här dokumentet.

Flera konventionella vägar

Flera konventionella vägar kan konfigureras genom att lägga till fler anrop till MapControllerRoute och MapAreaControllerRoute. På så sätt kan du definiera flera konventioner eller lägga till konventionella vägar som är dedikerade till en specifik åtgärd, till exempel:

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

Vägen blog i föregående kod är en dedikerad konventionell väg. Den kallas för en dedikerad konventionell väg eftersom:

Eftersom controller och action inte visas i routningsmallen "blog/{*article}" som parametrar:

  • De kan bara ha standardvärdena { controller = "Blog", action = "Article" }.
  • Den här rutten mappar alltid till åtgärden BlogController.Article.

/Blog, /Blog/Articleoch /Blog/{any-string} är de enda URL-sökvägarna som matchar bloggvägen.

Föregående exempel:

  • blog-rutt har högre prioritet för matchningar än default-rutt eftersom den läggs till först.
  • Är ett exempel på snigelformatsroutning där det är typiskt att ha ett artikelnamn som en del av URL:en.

Warning

I ASP.NET Core gör inte routning:

  • Definiera ett begrepp som kallas väg. UseRouting lägger till routningsmatchning till pipelinen för mellanprogram. Mellanprogrammet UseRouting tittar på den uppsättning slutpunkter som definierats i appen och väljer den bästa slutpunktsmatchningen baserat på begäran.
  • Ge garantier om exekveringsordningen för extensibilitet som IRouteConstraint eller IActionConstraint.

Se Routning för referensmaterial om routning.

Konventionell routningsordning

Konventionell routning matchar bara en kombination av åtgärd och kontrollant som definieras av appen. Detta är avsett att förenkla fall där konventionella vägar överlappar varandra. Lägga till vägar med hjälp av MapControllerRoute, MapDefaultControllerRouteoch MapAreaControllerRoute automatiskt tilldela ett ordervärde till sina slutpunkter baserat på den ordning som de anropas. Matchningar från en rutt som visas tidigare har högre prioritet. Konventionell routning är orderberoende. I allmänhet bör vägar med områden placeras tidigare eftersom de är mer specifika än vägar utan område. Dedikerade konventionella vägar med catch-all-routningsparametrar som {*article} kan göra en väg för girig, vilket innebär att den matchar URL:er som du avsåg att matchas av andra vägar. Placera de giriga rutterna senare i ruttabellen för att förhindra giriga matchningar.

Warning

En parameter kan matcha vägar felaktigt på grund av en bugg i routning. Appar som påverkas av den här buggen har följande egenskaper:

  • En allomfattande rutt, till exempel {**slug}"
  • Catch-all-vägen matchar inte begäranden som den ska matcha.
  • Om du tar bort andra rutter börjar den allomfattande rutten att fungera.

Se GitHub-buggar 18677 och 16579 till exempel fall som drabbat den här buggen.

En anmälningskorrigering för den här buggen finns i .NET Core 3.1.301 eller senare SDK. Följande kod anger en intern växel som åtgärdar felet:

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

Lösa tvetydiga åtgärder

När två slutpunkter matchar genom routning måste routning göra något av följande:

  • Välj den bästa kandidaten.
  • Kasta ett undantag.

Till exempel:

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

Den föregående kontrollanten definierar två åtgärder som matchar:

  • URL-sökvägen /Products33/Edit/17
  • Ruttdata { controller = Products33, action = Edit, id = 17 }.

Detta är ett typiskt mönster för MVC-styrenheter:

  • Edit(int) visar ett formulär för att redigera en produkt.
  • Edit(int, Product) bearbetar det publicerade formuläret.

För att fastställa rätt rutt:

  • Edit(int, Product) väljs när begäran är en HTTP POST.
  • Edit(int) väljs när HTTP-verbet är något annat. Edit(int) kallas vanligtvis via GET.

HttpPostAttribute, [HttpPost], tillhandahålls till routning så att det kan välja baserat på HTTP-metoden för begäran. HttpPostAttribute gör Edit(int, Product) till en bättre matchning än Edit(int).

Det är viktigt att förstå rollen för attribut som HttpPostAttribute. Liknande attribut definieras för andra HTTP-verb. I konventionell routning är det vanligt att åtgärder använder samma åtgärdsnamn när de ingår i ett visningsformulär och skickar formulärarbetsflödet. Se till exempel Granska de två redigeringsmetoderna.

Om routning inte kan välja en bästa kandidat genereras en AmbiguousMatchException lista över de flera matchade slutpunkterna.

Namn på konventionella vägar

Strängarna "blog" och "default" i följande exempel är konventionella vägnamn:

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

Routningsnamnen ger vägen ett logiskt namn. Den namngivna vägen kan användas för URL-generering. Med hjälp av en namngiven väg förenklas skapandet av URL när ordningen på vägar kan göra URL-genereringen komplicerad. Routningsnamn måste vara unika för hela programmet.

Routningsnamn:

  • Påverka inte URL-matchning eller hantering av begäranden.
  • Används endast för URL-generering.

Begreppet routningsnamn representeras i routning som IEndpointNameMetadata. Termen routningsnamn och slutpunktsnamn:

  • Är utbytbara.
  • Vilken som används i dokumentationen och koden beror på vilket API som beskrivs.

Attributroutning för REST API:er

REST API:er bör använda attributroutning för att modellera appens funktioner som en uppsättning resurser där åtgärder representeras av HTTP-verb.

Attributroutning använder en uppsättning attribut för att mappa åtgärder direkt till routningsmallar. Följande kod är typisk för ett REST API och används i nästa exempel:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

I den föregående koden anropas MapControllers för att kartlägga attributroutade kontrollanter.

I följande exempel:

  • HomeController matchar en uppsättning URL:er som liknar vad den vanliga standardvägen {controller=Home}/{action=Index}/{id?} matchar.
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);
    }
}

Åtgärden HomeController.Index körs för någon av URL-sökvägarna /, /Home, /Home/Indexeller /Home/Index/3.

Det här exemplet visar en viktig programmeringsskillnad mellan attributroutning och konventionell routning. Attributroutning kräver mer indata för att ange en väg. Den konventionella standardvägen hanterar vägar mer kortfattat. Attributroutning tillåter dock och kräver exakt kontroll över vilka routningsmallar som gäller för varje åtgärd.

Med attributroutning spelar kontrollanten och åtgärdsnamnen ingen roll där åtgärden matchas, såvida inte tokenbyte används. Följande exempel matchar samma URL:er som i föregående exempel:

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

Följande kod använder tokenersättning för action och 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();
    }
}

Följande kod gäller [Route("[controller]/[action]")] för kontrollanten:

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

I den föregående koden måste Index metodmallarna lägga till / eller ~/ till routemallarna. Routningsmallar som tillämpas på en åtgärd som börjar med / eller ~/ kombineras inte med routningsmallar som tillämpas på kontrollern.

Se Prioritet för routningsmall för information om val av routningsmall.

Namn på reserverad routning

Följande nyckelord är reserverade routningsparameternamn när du använder kontrollanter eller Razor sidor:

  • action
  • area
  • controller
  • handler
  • page

Att använda page som routningsparameter med attributroutning är ett vanligt fel. Att göra det resulterar i inkonsekvent och förvirrande beteende med URL-generering.

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

De särskilda parameternamnen används av URL-genereringen för att avgöra om en URL-genereringsåtgärd refererar till en Razor sida eller till en kontrollant.

Följande nyckelord är reserverade i kontexten för en Razor vy eller en Razor sida:

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

Dessa nyckelord ska inte användas för länkgenerationer, modellbundna parametrar eller egenskaper på toppnivå.

HTTP-verbmallar

ASP.NET Core har följande HTTP-verbmallar:

Routningsmallar

ASP.NET Core har följande routningsmallar:

Attributroutning med http-verbattribut

Tänk på följande kontrollant:

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

I koden ovan:

  • Varje åtgärd innehåller [HttpGet] attributet, som endast begränsar matchning till HTTP GET-begäranden.
  • Åtgärden GetProduct innehåller mallen "{id}" och läggs därför id till i mallen "api/[controller]" på kontrollanten. Mallen för metoder är "api/[controller]/{id}". Därför matchar den här åtgärden endast GET-begäranden för formuläret /api/test2/xyz,/api/test2/123/api/test2/{any string} osv.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • Åtgärden GetIntProduct innehåller mallen "int/{id:int}" . Delen :int av mallen begränsar id vägvärdena till strängar som kan konverteras till ett heltal. En GET-begäran till /api/test2/int/abc:
    • Matchar inte den här åtgärden.
    • Returnerar felet 404 Hittades inte .
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • Åtgärden GetInt2Product innehåller {id} i mallen, men begränsar id inte till värden som kan konverteras till ett heltal. En GET-begäran till /api/test2/int2/abc:
    • Passar denna rutt.
    • Modellbindningen kan inte konverteras abc till ett heltal. Parametern id för metoden är heltal.
    • Returnerar en 400 felaktig begäran eftersom modellbindningen inte kunde konverteras abc till ett heltal.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Attributroutning kan använda HttpMethodAttribute attribut som HttpPostAttribute, HttpPutAttributeoch HttpDeleteAttribute. Alla HTTP-verbsattribut accepterar en vägmall. I följande exempel visas två åtgärder som matchar samma vägmall:

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

Använda URL-sökvägen /products3:

  • Åtgärden MyProductsController.ListProducts körs när HTTP-verbet är GET.
  • Åtgärden MyProductsController.CreateProduct körs när HTTP-verbet är POST.

När du skapar ett REST API är det ovanligt att du behöver använda [Route(...)] en åtgärdsmetod eftersom åtgärden accepterar alla HTTP-metoder. Det är bättre att använda det mer specifika HTTP-verbattributet för att vara exakt om vad ditt API stöder. Klienter för REST API:er förväntas veta vilka sökvägar och HTTP-verb som mappar till specifika logiska åtgärder.

REST API:er bör använda attributroutning för att modellera appens funktioner som en uppsättning resurser där åtgärder representeras av HTTP-verb. Det innebär att många åtgärder, till exempel GET och POST på samma logiska resurs, använder samma URL. Attributroutning ger en kontrollnivå som krävs för att noggrant utforma ett API:s offentliga slutpunktslayout.

Eftersom en attributväg gäller för en specifik åtgärd är det enkelt att göra parametrar som krävs som en del av definitionen av routningsmallen. I följande exempel id krävs som en del av URL-sökvägen:

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

Åtgärden Products2ApiController.GetProduct(int) :

  • Körs med URL-sökväg som /products2/3
  • Körs inte med URL-sökvägen /products2.

Attributet [Förbrukar] tillåter en åtgärd för att begränsa innehållstyperna för begäranden som stöds. Mer information finns i Definiera innehållstyper för begäranden som stöds med attributet Förbrukar.

Se Routning för en fullständig beskrivning av routningsmallar och relaterade alternativ.

Mer information om [ApiController]finns i ApiController-attribut.

Routningsnamn

Följande kod definierar vägnamnet Products_List:

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

Routningsnamn kan användas för att generera en URL baserat på en specifik väg. Routningsnamn:

  • Ha ingen inverkan på url-matchningsbeteendet för routning.
  • Används endast för URL-generering.

Routningsnamn måste vara unika för hela programmet.

Kontrastera föregående kod med den konventionella standardvägen, som definierar parametern id som valfri ({id?}). Möjligheten att exakt ange API:er har fördelar, till exempel att tillåta /products och /products/5 skickas till olika åtgärder.

Kombinera attributvägar

För att göra attributdirigering mindre repetitiv kombineras routningsattributen på kontrollanten med routningsattribut för de enskilda åtgärderna. Alla vägmallar som definierats på kontrollern läggs till före vägmallarna på åtgärderna. Om du placerar ett routningsattribut på kontrollanten används attributdirigering för alla åtgärder i kontrollanten.

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

I föregående exempel:

  • URL-sökvägen /products kan matcha ProductsApi.ListProducts
  • URL-sökvägen /products/5 kan matcha ProductsApi.GetProduct(int).

Båda dessa åtgärder matchar bara HTTP GET eftersom de har markerats med attributet [HttpGet] .

Routningsmallar som tillämpas på en åtgärd som börjar med / eller ~/ kombineras inte med routningsmallar som tillämpas på kontrollern. Följande exempel matchar en uppsättning URL-sökvägar som liknar standardvägen.

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

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

I följande tabell förklaras attributen [Route] i föregående kod:

Attribute Kombinerar med [Route("Home")] Definierar routningsmall
[Route("")] Yes "Home"
[Route("Index")] Yes "Home/Index"
[Route("/")] No ""
[Route("About")] Yes "Home/About"

Ordning för attributrutter

Routning skapar ett träd och matchar alla slutpunkter samtidigt:

  • Routningsposterna beter sig som om de vore placerade i en idealisk ordning.
  • De mest specifika rutterna har möjlighet att köras före de mer allmänna rutterna.

Till exempel är en attributväg som blog/search/{topic} är mer specifik än en attributväg som blog/{*article}. Vägen blog/search/{topic} har högre prioritet, som standard, eftersom den är mer specifik. Med hjälp av konventionell routning ansvarar utvecklaren för att placera vägar i önskad ordning.

Attributvägar kan konfigurera en ordning med hjälp av egenskapen Order. Alla routningsattribut som tillhandahålls av ramverket är Order . Vägar bearbetas enligt en stigande typ av Order egenskapen. Standardordningen är 0. Ange en rutt med Order = -1 innan rutter som inte anger någon ordning. Ställ in en rutt genom Order = 1 som körs efter standardruttordning.

Undvik beroende på Order. Om en apps URL-utrymme kräver explicita ordervärden för att dirigeras korrekt är det sannolikt förvirrande även för klienter. I allmänhet väljer attributroutning rätt väg med URL-matchning. Om standardordningen som används för URL-generering inte fungerar är det vanligtvis enklare att använda ett routningsnamn som åsidosättning än att tillämpa Order egenskapen.

Överväg följande två kontrollanter som båda definierar routningsmatchningen /home:

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

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

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

Om du begär /home med föregående kod genereras ett undantag som liknar följande:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

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

Om du lägger Order till något av routningsattributen löses tvetydigheten:

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

Med den föregående koden kör /home slutpunkten HomeController.Index. För att komma till MyDemoController.MyIndex, begär /home/MyIndex. Note:

  • Föregående kod är ett exempel eller dålig routningsdesign. Den användes för att illustrera egenskapen Order .
  • Egenskapen Order löser bara tvetydigheten, den mallen kan inte matchas. Det vore bättre att ta bort mallen [Route("Home")] .

Se Razor Sidors routnings- och appkonventioner: Routningsordning för information om routningsordning med Razor Sidor.

I vissa fall returneras ett HTTP 500-fel med tvetydiga vägar. Använd loggning för att se vilka ändpunkter som orsakade AmbiguousMatchException.

Ersättning av tokens i vägmallar [kontroller], [aktion], [område]

För enkelhetens skull stöder attributvägar tokenbyte genom att omsluta en token i hakparenteser ([, ]). Token , [action]och [area] ersätts med värdena för åtgärdsnamnet[controller], områdesnamnet och kontrollantnamnet från åtgärden där vägen definieras:

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

I koden ovan:

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

Tokenbyte sker som det sista steget för att skapa attributvägarna. Föregående exempel beter sig på samma sätt som följande kod:

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

Om du läser detta på ett annat språk än engelska kan du meddela oss i det här GitHub-diskussionsproblemet om du vill se kodkommentarna på ditt modersmål.

Attributvägar kan också kombineras med arv. Detta är kraftfullt kombinerat med tokenbyte. Tokenbyte gäller även för routningsnamn som definierats av attributvägar. [Route("[controller]/[action]", Name="[controller]_[action]")]genererar ett unikt vägnamn för varje åtgärd:

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

Om du vill matcha den literala tokenbyteslimiten [ eller ], tar du bort den genom att upprepa tecknet ([[ eller ]]).

Använda en parametertransformator för att anpassa tokenbyte

Tokenbyte kan anpassas med hjälp av en parametertransformator. En parametertransformator implementerar IOutboundParameterTransformer och transformerar värdet för parametrar. En anpassad SlugifyParameterTransformer parametertransformator ändrar till exempel routningsvärdet SubscriptionManagement till 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();
    }
}

RouteTokenTransformerConvention Är en konvention för programmodell som:

  • Tillämpar en parametertransformator på alla attributvägar i ett program.
  • Anpassar attributvägens tokenvärden när de ersätts.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

ListAll Föregående metod matchar /subscription-management/list-all.

RouteTokenTransformerConvention Är registrerad som ett alternativ:

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

Se MDN-webbdokument på Slug för definitionen av Slug.

Warning

När du använder System.Text.RegularExpressions för att bearbeta ej betrodda indata skickar du en timeout. En obehörig användare kan ange indata för att RegularExpressions orsaka en Denial-of-Service-attack. ASP.NET Core Frameworks API som använder RegularExpressions har en timeout.

Flera attributvägar

Attributroutning stöder definition av flera vägar som når samma åtgärd. Den vanligaste användningen av detta är att efterlikna beteendet för den vanliga konventionella vägen enligt följande exempel:

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

Om du placerar flera routningsattribut på kontrollanten innebär det att var och en kombineras med vart och ett av routningsattributen på åtgärdsmetoderna:

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

Alla villkor för HTTP-verbvägen implementerar IActionConstraint.

När flera routattribut som implementerar IActionConstraint placeras till en åtgärd:

  • Varje åtgärdsbegränsning kombineras med routningsmallen som tillämpas på kontrollanten.
[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();
    }
}

Om du använder flera vägar för åtgärder kan det verka användbart och kraftfullt. Det är bättre att hålla appens URL-utrymme grundläggande och väldefinierat. Använd flera vägar för åtgärder endast där det behövs, till exempel för att stödja befintliga kunder.

Ange valfria parametrar för attributväg, standardvärden och begränsningar

Attributvägar stöder samma infogade syntax som konventionella vägar för att ange valfria parametrar, standardvärden och begränsningar.

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

I föregående kod [HttpPost("product14/{id:int}")] tillämpar du en routningsbegränsning. Åtgärden Products14Controller.ShowProduct matchas endast av URL-sökvägar som /product14/3. Routningsmalldelen {id:int} begränsar segmentet till enbart heltal.

En detaljerad beskrivning av routningsmallssyntaxen finns i Referens för routningsmall .

Anpassade routningsattribut med IRouteTemplateProvider

Alla routningsattribut implementerar IRouteTemplateProvider. ASP.NET Core körmiljö:

  • Söker efter attribut på kontrollantklasser och åtgärdsmetoder när appen startar.
  • Använder attributen som implementerar IRouteTemplateProvider för att skapa den initiala uppsättningen rutter.

Implementera IRouteTemplateProvider för att definiera anpassade routningsattribut. Varje IRouteTemplateProvider gör att du kan definiera en enskild rutt med en anpassad ruttmall, ordning och namn:

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

Get Föregående metod returnerar Order = 2, Template = api/MyTestApi.

Använda programmodell för att anpassa attributvägar

Programmodellen:

  • En objektmodell skapas vid start i Program.cs.
  • Innehåller alla metadata som används av ASP.NET Core för att dirigera och köra åtgärderna i en app.

Programmodellen innehåller alla data som samlas in från routningsattribut. Data från routningsattribut tillhandahålls av implementeringen IRouteTemplateProvider . Conventions:

  • Kan skrivas för att ändra programmodellen för att anpassa hur routning fungerar.
  • När appen startar läses de.

Det här avsnittet visar ett grundläggande exempel på hur du anpassar routning med hjälp av programmodellen. Följande kod gör vägarna ungefär i linje med projektets mappstruktur.

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

Följande kod förhindrar att konventionen namespace tillämpas på kontroller som är attributroutade:

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

Följande styrenhet använder NamespaceRoutingConventiontill exempel inte :

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

NamespaceRoutingConvention.Apply-metoden:

  • Gör ingenting om kontrollen är attribut-routerad.
  • Anger kontrollantmallen baserat på namespace, med basen namespace borttagen.

NamespaceRoutingConvention Kan användas i 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();

Tänk till exempel på följande kontrollant:

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

I koden ovan:

  • Basen namespace är My.Application.
  • Det fullständiga namnet på den föregående kontrollanten är My.Application.Admin.Controllers.UsersController.
  • Anger NamespaceRoutingConvention kontrollantmallen till Admin/Controllers/Users/[action]/{id?.

NamespaceRoutingConvention Kan också användas som ett attribut på en kontrollant:

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

Blandad routning: Attributroutning jämfört med konventionell routning

ASP.NET Core-appar kan blanda användningen av konventionell routning och attributroutning. Det är vanligt att använda konventionella vägar för kontrollanter som betjänar HTML-sidor för webbläsare och attributroutning för kontrollanter som betjänar REST API:er.

Åtgärder dirigeras antingen konventionellt eller dirigeras med attribut. Om du placerar en väg på kontrollanten eller åtgärden dirigeras attributet. Åtgärder som definierar attributvägar kan inte nås via konventionella vägar och vice versa. Några routningsattribut på kontrollern gör att alla åtgärder i kontrollern blir dirigerade genom attributet.

Attributroutning och konventionell routning använder samma routningsmotor.

Routning med specialtecken

Routning med specialtecken kan leda till oväntade resultat. Tänk dig till exempel en kontrollant med följande åtgärdsmetod:

[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;
}

När string id innehåller följande kodade värden kan oväntade resultat inträffa:

ASCII Encoded
/ %2F
+

Routningsparametrar är inte alltid URL-avkodade. Det här problemet kan åtgärdas i framtiden. Mer information finns i det här GitHub-problemet;

URL-generering och omgivande värden

Appar kan använda genereringsfunktioner för routnings-URL för att generera URL-länkar till åtgärder. Att generera URL:er eliminerar hårdkodande URL:er, vilket gör koden mer robust och underhållsbar. Det här avsnittet fokuserar på url-genereringsfunktionerna som tillhandahålls av MVC och omfattar endast grunderna för hur URL-generering fungerar. Se Routning för en detaljerad beskrivning av URL-generering.

Gränssnittet IUrlHelper är det underliggande elementet i infrastrukturen mellan MVC och routning för URL-generering. En instans av IUrlHelper är tillgänglig via Url egenskapen i kontrollanter, vyer och visningskomponenter.

I följande exempel IUrlHelper används gränssnittet via egenskapen Controller.Url för att generera en URL till en annan åtgärd.

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

Om appen använder den vanliga standardvägen är värdet för variabeln url URL-sökvägssträngen /UrlGeneration/Destination. Den här URL-sökvägen skapas genom routning genom att kombinera:

  • Routningsvärdena från den aktuella begäran, som kallas för omgivande värden.
  • Värdena som skickas till Url.Action och de värden som ersätter dessa i routmallen:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Varje ruttparameter i vägmallen får sitt värde ersatt av att matcha namn med värden och omgivande värden. En vägparameter som inte har något värde kan:

  • Använd ett standardvärde om det har ett.
  • Hoppas över om det är valfritt. Till exempel id från vägmallen {controller}/{action}/{id?}.

URL-genereringen misslyckas om någon obligatorisk vägparameter inte har något motsvarande värde. Om URL-genereringen misslyckas för en väg provas nästa väg tills alla vägar har provats eller en matchning hittas.

Föregående exempel på Url.Action förutsätter konventionell routning. URL-generering fungerar på samma sätt med attributroutning, även om begreppen skiljer sig. Med konventionell routning:

  • Vägvärdena används för att expandera en mall.
  • Vägvärdena för controller och action visas vanligtvis i mallen. Detta fungerar eftersom URL:erna som matchas av routning följer en konvention.

I följande exempel används attributroutning:

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

Åtgärden Source i föregående kod genererar custom/url/to/destination.

LinkGenerator lades till i ASP.NET Core 3.0 som ett alternativ till IUrlHelper. LinkGenerator erbjuder liknande men mer flexibla funktioner. Varje metod på IUrlHelper har också en motsvarande uppsättning metoder LinkGenerator .

Generera URL:er efter åtgärdsnamn

Url.Action, LinkGenerator.GetPathByAction och alla relaterade överlagringar är alla utformade för att generera målslutpunkten genom att ange ett kontrollantnamn och åtgärdsnamn.

När du använder Url.Action, tillhandahålls de aktuella ruttvärdena för controller och action av körningstiden.

  • Värdet för controller och action är en del av både omgivande värden och värden. Metoden Url.Action använder alltid de aktuella värdena action för och controller och genererar en URL-sökväg som dirigerar till den aktuella åtgärden.

Routning försöker använda värdena i omgivande värden för att fylla i information som inte angavs när en URL genererades. Överväg en väg som {a}/{b}/{c}/{d} med omgivande värden { a = Alice, b = Bob, c = Carol, d = David }:

  • Routning har tillräckligt med information för att generera en URL utan ytterligare värden.
  • Routning har tillräckligt med information eftersom alla vägparametrar har ett värde.

Om värdet { d = Donovan } läggs till:

  • Värdet { d = David } ignoreras.
  • Den genererade URL-sökvägen är Alice/Bob/Carol/Donovan.

Varning! URL-sökvägar är hierarkiska. Om värdet { c = Cheryl } läggs till i föregående exempel:

  • Båda värdena { c = Carol, d = David } ignoreras.
  • Det finns inte längre något värde för d och URL-genereringen misslyckas.
  • Önskade värden för c och d måste anges för att generera en URL.

Du kan förvänta dig att stöta på det här problemet med standardvägen {controller}/{action}/{id?}. Det här problemet är sällsynt i praktiken eftersom Url.Action alltid uttryckligen anger ett controller och action -värde.

Flera överlagringar av URL.Action använder ett vägvärdesobjekt för att ange värden för andra vägparametrar än controller och action. Objektet för vägvärden används ofta med id. Till exempel Url.Action("Buy", "Products", new { id = 17 }). Objektet för ruttvärden

  • Enligt konvention är det vanligtvis ett objekt av anonym typ.
  • Kan vara en IDictionary<> eller en POCO).

Eventuella ytterligare vägvärden som inte matchar routningsparametrar placeras i frågesträngen.

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

Föregående kod genererar /Products/Buy/17?color=red.

Följande kod genererar en absolut 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!);
}

Om du vill skapa en absolut URL använder du något av följande:

  • En överbelastning som accepterar en protocol. Till exempel föregående kod.
  • LinkGenerator.GetUriByAction, som genererar absoluta URI:er som standard.

Generera URL:er efter väg

Föregående kod visade hur du genererade en URL genom att skicka in kontrollanten och åtgärdsnamnet. IUrlHelper innehåller också url.RouteUrl-serien med metoder. Dessa metoder liknar Url.Action, men de kopierar inte de aktuella värdena action för och controller till vägvärdena. Den vanligaste användningen av Url.RouteUrl:

  • Anger ett vägnamn för att generera URL:en.
  • I allmänhet anger inte en kontrollant eller åtgärdsnamn.
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();
    }

Följande Razor fil genererar en HTML-länk till Destination_Route:

<h1>Test Links</h1>

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

Generera URL:er i HTML och Razor

IHtmlHelper HtmlHelper innehåller metoderna Html.BeginForm och Html.ActionLink för att generera <form><a> respektive element. Dessa metoder använder metoden Url.Action för att generera en URL och de accepterar liknande argument. Följeslagare Url.RouteUrl för HtmlHelper är Html.BeginRouteForm och Html.RouteLink som har liknande funktioner.

TagHelpers genererar URL:er via form TagHelper och <a> TagHelper. Båda dessa använder IUrlHelper för deras implementering. För mer information, se Tag Helpers i formulär.

I vyer, IUrlHelper finns tillgänglig via Url den egenskapen för alla ad hoc-URL-genereringar som inte omfattas av ovanstående.

URL-generering i åtgärdsresultat

De föregående exemplen visade hur man använder IUrlHelper i en kontroller. Det vanligaste syftet med en kontroller är att generera en URL som en del av ett åtgärdsresultat.

Basklasserna ControllerBase och Controller tillhandahåller bekvämlighetsmetoder för åtgärdsresultat som refererar till en annan åtgärd. En vanlig användning är att omdirigera efter att ha accepterat användarindata:

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

Metoder för åtgärdsresultat som RedirectToAction och CreatedAtAction följer ett liknande mönster som metoderna hos IUrlHelper.

Specialfall för dedikerade konventionella vägar

Konventionell routning kan använda en särskild typ av vägdefinition som kallas en dedikerad konventionell väg. I följande exempel är den väg som heter blog en dedikerad konventionell väg:

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

Med hjälp av de föregående ruttdefinitionerna genererar Url.Action("Index", "Home") URL-sökvägen / med rutt default, men varför? Du kan gissa att vägvärdena { controller = Home, action = Index } räcker för att generera en URL med , blogoch resultatet blir /blog?action=Index&controller=Home.

Dedikerade konventionella vägar förlitar sig på ett särskilt beteende med standardvärden som inte har en motsvarande vägparameter som förhindrar att vägen blir för girig med URL-generering. I det här fallet är { controller = Blog, action = Article }standardvärdena , och varken controller eller action visas som en vägparameter. När routningen utför URL-generering måste de angivna värdena matcha standardvärdena. URL-genereringen med hjälp av blog misslyckas eftersom värdena { controller = Home, action = Index } inte matchar { controller = Blog, action = Article }. Routning faller sedan tillbaka för att prova default, vilket lyckas.

Areas

Områden är en MVC-funktion som används för att organisera relaterade funktioner i en grupp som en separat:

  • Routningsnamnområde för kontrollantåtgärder.
  • Mappstruktur för vyer.

Med hjälp av områden kan en app ha flera kontrollanter med samma namn, så länge de har olika områden. Med hjälp av områden skapas en hierarki för routning genom att lägga till en annan routningsparameter, area till controller och action. I det här avsnittet beskrivs hur routning interagerar med områden. Mer information om hur områden används med vyer finns i Områden .

I följande exempel konfigureras MVC att använda standardrutt för konventionell väg och en area rutt för en area som heter Blog.

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

I föregående kod MapAreaControllerRoute anropas för att skapa "blog_route". Den andra parametern, "Blog", är områdesnamnet.

När du matchar en URL-sökväg som /Manage/Users/AddUser"blog_route" genererar vägen vägvärdena { area = Blog, controller = Users, action = AddUser }. Routningsvärdet area genereras av ett standardvärde för area. Den väg som skapas av MapAreaControllerRoute motsvarar följande:

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

MapAreaControllerRoute skapar en väg med både ett standardvärde och en begränsning för area att använda det angivna områdesnamnet, i det här fallet Blog. Standardvärdet säkerställer att vägen alltid genererar { area = Blog, ... }, villkoret kräver värdet { area = Blog, ... } för URL-generering.

Konventionell routning är orderberoende. I allmänhet bör vägar med områden placeras tidigare eftersom de är mer specifika än vägar utan område.

Med hjälp av föregående exempel matchar vägvärdena { area = Blog, controller = Users, action = AddUser } följande åtgärd:

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

Attributet [Area] är det som anger en kontrollant som en del av ett område. Den här styrenheten finns i området Blog . Kontrollanter utan attribut [Area] är inte medlemmar i något område och matchar inte när area routningsvärdet tillhandahålls av routning. I följande exempel kan endast den första kontrollanten i listan matcha vägvärdena { area = Blog, controller = Users, action = AddUser }.

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

Namnområdet för varje kontrollant visas här för fullständighet. Om de föregående kontrollanterna använde samma namnområde genereras ett kompilatorfel. Klassnamnområden har ingen effekt på MVC:s routning.

De två första kontrollanterna är medlemmar i områden och matchar bara när deras respektive områdesnamn anges av area vägvärdet. Den tredje styrenheten är inte medlem i något område och kan bara matcha när inget värde för area tillhandahålls av routning.

När det gäller att matcha inget värde är frånvaron av area värdet densamma som om värdet för area var null eller den tomma strängen.

När du kör en åtgärd i ett område är routningsvärdet för area tillgängligt som ett omgivande värde för routning som ska användas för URL-generering. Det innebär att som standard fungerar områden som är klibbiga för URL-generering, vilket visas i följande exempel.

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

Följande kod genererar en URL till /Zebra/Users/AddUser:

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

Åtgärdsdefinition

Offentliga metoder på en kontrollant, förutom de med attributet NonAction , är åtgärder.

Exempelkod

Felsökningsdiagnostik

För detaljerade routningsdiagnostikutdata anger du Logging:LogLevel:Microsoft till Debug. I utvecklingsmiljön anger du loggnivån i appsettings.Development.json:

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

ASP.NET Core-styrenheter använder routningsmellanprogrammet för att matcha URL:erna för inkommande begäranden och mappa dem till åtgärder. Routningsmallar:

  • Definieras i startkod eller attribut.
  • Beskriv hur URL-sökvägar matchas med åtgärder.
  • Används för att generera URL:er för länkar. De genererade länkarna returneras vanligtvis i svar.

Åtgärder är antingen konventionellt dirigerade eller attributroutade. Att placera en rutt på kontrollern eller åtgärden gör den attributbaserad. Mer information finns i Blandad routning .

Det här dokumentet:

  • Förklarar interaktionerna mellan MVC och routning:
  • Refererar till standarddirigeringssystemet som lagts till i ASP.NET Core 3.0, som kallas slutpunktsroutning. Det är möjligt att använda styrenheter med den tidigare versionen av routning i kompatibilitetssyfte. Anvisningar finns i migreringsguiden 2.2-3.0 . Se 2.2-versionen av det här dokumentet för referensmaterial i det äldre routningssystemet.

Konfigurera konventionell väg

Startup.Configure har vanligtvis kod som liknar följande när du använder konventionell routning:

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

Inuti anropet till UseEndpoints, MapControllerRoute används för att skapa en enda rutt. Den enda vägen heter default route. De flesta appar med kontrollanter och vyer använder en routningsmall som liknar default vägen. REST API:er bör använda attributroutning.

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

  • Matchar en URL-sökväg som /Products/Details/5

  • Extraherar vägvärdena { controller = Products, action = Details, id = 5 } genom att tokenisera sökvägen. Extrahering av vägvärden resulterar i en matchning om appen har en styrenhet med namnet ProductsController och en Details åtgärd:

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

    MyDisplayRouteInfo tillhandahålls av Rick.Docs.Samples.RouteInfo NuGet-paketet och visar väginformation.

  • /Products/Details/5 modellen binder värdet id = 5 för att ange parametern id till 5. Mer information finns i Modellbindning .

  • {controller=Home} definierar Home som standard controller.

  • {action=Index} definierar Index som standard action.

  • Tecknet ? i {id?} definierar id som valfritt.

  • Standard- och valfria vägparametrar behöver inte finnas i URL-sökvägen för en matchning. En detaljerad beskrivning av routningsmallssyntaxen finns i Referens för routningsmall .

  • Matchar URL-sökvägen /.

  • Genererar vägvärdena { controller = Home, action = Index }.

Värdena för controller och action använder standardvärdena. id ger inget värde eftersom det inte finns något motsvarande segment i URL-sökvägen. / matchar endast om det finns en HomeController-åtgärd och Index.

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

Med hjälp av föregående kontrollantdefinition och vägmall HomeController.Index körs åtgärden för följande URL-sökvägar:

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

URL-sökvägen / använder standardkontrollanterna Home och Index åtgärden för routningsmallen. URL-sökvägen /Home använder standardåtgärden Index för routningsmallen.

Bekvämlighetsmetoden MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Replaces:

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

Important

Routning konfigureras med hjälp av UseRoutingmellanprogrammet , MapControllerRouteoch MapAreaControllerRoute . Så här använder du kontroller:

Konventionell routning

Konventionell routning används med styrenheter och vyer. Vägen default :

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

Föregående är ett exempel på en konventionell väg. Den kallas för konventionell routning eftersom den upprättar en konvention för URL-sökvägar:

  • Det första sökvägssegmentet, {controller=Home}, mappar till kontrollantnamnet.
  • Det andra segmentet, {action=Index}, mappar till åtgärdsnamnet .
  • Det tredje segmentet {id?} används för en valfri id. ? i {id?} gör det valfritt. id används för att mappa till en modellentitet.

Med hjälp av den här default rutten, URL-sökvägen:

  • /Products/List mappar till åtgärden ProductsController.List .
  • /Blog/Article/17 mappar till BlogController.Article och binder oftast parametern id till 17.

Den här mappningen:

  • Baseras endast på kontrollanten och åtgärdsnamnen.
  • Baseras inte på namnrymder, källfilplatser eller metodparametrar.

Med konventionell routning med standardvägen kan du skapa appen utan att behöva komma med ett nytt URL-mönster för varje åtgärd. För en app med CRUD-formatåtgärder , med konsekvens för URL:er mellan kontrollanter:

  • Hjälper till att förenkla koden.
  • Gör användargränssnittet mer förutsägbart.

Warning

I id föregående kod definieras som valfri av routningsmallen. Åtgärder kan köras utan det valfria ID som anges som en del av URL:en. Vanligtvis när id utelämnas från URL:en:

  • id sätts till 0 av modellbindning.
  • Ingen entitet hittades i databasen som matchar id == 0.

Attributroutning ger detaljerad kontroll för att göra ID obligatoriskt för vissa åtgärder men inte för andra. Enligt konventionen innehåller dokumentationen valfria parametrar som id när de sannolikt kommer att visas i korrekt användning.

De flesta appar bör välja ett grundläggande och beskrivande routningsschema så att URL:er är läsbara och meningsfulla. Standardvägen för konventionell väg {controller=Home}/{action=Index}/{id?}:

  • Stöder ett grundläggande och beskrivande routningsschema.
  • Är en användbar startpunkt för användargränssnittsbaserade appar.
  • Är den enda vägmall som behövs för många webbgränssnittsappar. För större webbgränssnittsappar är en annan väg som använder Områden ofta allt som behövs.

MapControllerRoute och MapAreaRoute :

  • Tilldela automatiskt ett ordervärde till sina slutpunkter baserat på den ordning som de anropas.

Slutpunktsroutning i ASP.NET Core 3.0 eller senare:

  • Har inget begrepp om vägar.
  • Ger inte ordergarantier för körning av utökningsbarhet, alla slutpunkter bearbetas samtidigt.

Aktivera loggning för att se hur de inbyggda routningsimplementeringarna, till exempel Route, matchar begäranden.

Attributroutning förklaras senare i det här dokumentet.

Flera konventionella vägar

Flera konventionella vägar kan läggas till inuti UseEndpoints genom att lägga till fler anrop till MapControllerRoute och MapAreaControllerRoute. På så sätt kan du definiera flera konventioner eller lägga till konventionella vägar som är dedikerade till en specifik åtgärd, till exempel:

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

Vägen blog i föregående kod är en dedikerad konventionell väg. Den kallas för en dedikerad konventionell väg eftersom:

Eftersom controller och action inte visas i routningsmallen "blog/{*article}" som parametrar:

  • De kan bara ha standardvärdena { controller = "Blog", action = "Article" }.
  • Den här rutten mappar alltid till åtgärden BlogController.Article.

/Blog, /Blog/Articleoch /Blog/{any-string} är de enda URL-sökvägarna som matchar bloggvägen.

Föregående exempel:

  • blog-rutt har högre prioritet för matchningar än default-rutt eftersom den läggs till först.
  • Är ett exempel på snigelformatsroutning där det är typiskt att ha ett artikelnamn som en del av URL:en.

Warning

I ASP.NET Core 3.0 eller senare gör routning inte följande:

  • Definiera ett begrepp som kallas väg. UseRouting lägger till routningsmatchning till pipelinen för mellanprogram. Mellanprogrammet UseRouting tittar på den uppsättning slutpunkter som definierats i appen och väljer den bästa slutpunktsmatchningen baserat på begäran.
  • Ge garantier om exekveringsordningen för extensibilitet som IRouteConstraint eller IActionConstraint.

Se Routning för referensmaterial om routning.

Konventionell routningsordning

Konventionell routning matchar bara en kombination av åtgärd och kontrollant som definieras av appen. Detta är avsett att förenkla fall där konventionella vägar överlappar varandra. Lägga till vägar med hjälp av MapControllerRoute, MapDefaultControllerRouteoch MapAreaControllerRoute automatiskt tilldela ett ordervärde till sina slutpunkter baserat på den ordning som de anropas. Matchningar från en rutt som visas tidigare har högre prioritet. Konventionell routning är orderberoende. I allmänhet bör vägar med områden placeras tidigare eftersom de är mer specifika än vägar utan område. Dedikerade konventionella vägar med catch-all-routningsparametrar som {*article} kan göra en väg för girig, vilket innebär att den matchar URL:er som du avsåg att matchas av andra vägar. Placera de giriga rutterna senare i ruttabellen för att förhindra giriga matchningar.

Warning

En parameter kan matcha vägar felaktigt på grund av en bugg i routning. Appar som påverkas av den här buggen har följande egenskaper:

  • En allomfattande rutt, till exempel {**slug}"
  • Catch-all-vägen matchar inte begäranden som den ska matcha.
  • Om du tar bort andra rutter börjar den allomfattande rutten att fungera.

Se GitHub-buggar 18677 och 16579 till exempel fall som drabbat den här buggen.

En anmälningskorrigering för den här buggen finns i .NET Core 3.1.301 eller senare SDK. Följande kod anger en intern växel som åtgärdar felet:

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

Lösa tvetydiga åtgärder

När två slutpunkter matchar genom routning måste routning göra något av följande:

  • Välj den bästa kandidaten.
  • Kasta ett undantag.

Till exempel:

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

Den föregående kontrollanten definierar två åtgärder som matchar:

  • URL-sökvägen /Products33/Edit/17
  • Ruttdata { controller = Products33, action = Edit, id = 17 }.

Detta är ett typiskt mönster för MVC-styrenheter:

  • Edit(int) visar ett formulär för att redigera en produkt.
  • Edit(int, Product) bearbetar det publicerade formuläret.

För att fastställa rätt rutt:

  • Edit(int, Product) väljs när begäran är en HTTP POST.
  • Edit(int) väljs när HTTP-verbet är något annat. Edit(int) kallas vanligtvis via GET.

HttpPostAttribute, [HttpPost], tillhandahålls till routning så att det kan välja baserat på HTTP-metoden för begäran. HttpPostAttribute gör Edit(int, Product) till en bättre matchning än Edit(int).

Det är viktigt att förstå rollen för attribut som HttpPostAttribute. Liknande attribut definieras för andra HTTP-verb. I konventionell routning är det vanligt att åtgärder använder samma åtgärdsnamn när de ingår i ett visningsformulär och skickar formulärarbetsflödet. Se till exempel Granska de två redigeringsmetoderna.

Om routning inte kan välja en bästa kandidat genereras en AmbiguousMatchException lista över de flera matchade slutpunkterna.

Namn på konventionella vägar

Strängarna "blog" och "default" i följande exempel är konventionella vägnamn:

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

Routningsnamnen ger vägen ett logiskt namn. Den namngivna vägen kan användas för URL-generering. Med hjälp av en namngiven väg förenklas skapandet av URL när ordningen på vägar kan göra URL-genereringen komplicerad. Routningsnamn måste vara unika för hela programmet.

Routningsnamn:

  • Påverka inte URL-matchning eller hantering av begäranden.
  • Används endast för URL-generering.

Begreppet routningsnamn representeras i routning som IEndpointNameMetadata. Termen routningsnamn och slutpunktsnamn:

  • Är utbytbara.
  • Vilken som används i dokumentationen och koden beror på vilket API som beskrivs.

Attributroutning för REST API:er

REST API:er bör använda attributroutning för att modellera appens funktioner som en uppsättning resurser där åtgärder representeras av HTTP-verb.

Attributroutning använder en uppsättning attribut för att mappa åtgärder direkt till routningsmallar. Följande StartUp.Configure kod är typisk för ett REST API och används i nästa exempel:

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

I föregående kod MapControllers anropas inuti UseEndpoints för att mappa attributroutade kontrollanter.

I följande exempel:

  • HomeController matchar en uppsättning URL:er som liknar vad den vanliga standardvägen {controller=Home}/{action=Index}/{id?} matchar.
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);
    }
}

Åtgärden HomeController.Index körs för någon av URL-sökvägarna /, /Home, /Home/Indexeller /Home/Index/3.

Det här exemplet visar en viktig programmeringsskillnad mellan attributroutning och konventionell routning. Attributroutning kräver mer indata för att ange en väg. Den konventionella standardvägen hanterar vägar mer kortfattat. Attributroutning tillåter dock och kräver exakt kontroll över vilka routningsmallar som gäller för varje åtgärd.

Med attributroutning spelar kontrollanten och åtgärdsnamnen ingen roll där åtgärden matchas, såvida inte tokenbyte används. Följande exempel matchar samma URL:er som i föregående exempel:

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

Följande kod använder tokenersättning för action och 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();
    }
}

Följande kod gäller [Route("[controller]/[action]")] för kontrollanten:

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

I den föregående koden måste Index metodmallarna lägga till / eller ~/ till routemallarna. Routningsmallar som tillämpas på en åtgärd som börjar med / eller ~/ kombineras inte med routningsmallar som tillämpas på kontrollern.

Se Prioritet för routningsmall för information om val av routningsmall.

Namn på reserverad routning

Följande nyckelord är reserverade routningsparameternamn när du använder kontrollanter eller Razor sidor:

  • action
  • area
  • controller
  • handler
  • page

Att använda page som routningsparameter med attributroutning är ett vanligt fel. Att göra det resulterar i inkonsekvent och förvirrande beteende med URL-generering.

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

De särskilda parameternamnen används av URL-genereringen för att avgöra om en URL-genereringsåtgärd refererar till en Razor sida eller till en kontrollant.

Följande nyckelord är reserverade i kontexten för en Razor vy eller en Razor sida:

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

Dessa nyckelord ska inte användas för länkgenerationer, modellbundna parametrar eller egenskaper på toppnivå.

HTTP-verbmallar

ASP.NET Core har följande HTTP-verbmallar:

Routningsmallar

ASP.NET Core har följande routningsmallar:

Attributroutning med http-verbattribut

Tänk på följande kontrollant:

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

I koden ovan:

  • Varje åtgärd innehåller [HttpGet] attributet, som endast begränsar matchning till HTTP GET-begäranden.
  • Åtgärden GetProduct innehåller mallen "{id}" och läggs därför id till i mallen "api/[controller]" på kontrollanten. Mallen för metoder är "api/[controller]/{id}". Därför matchar den här åtgärden endast GET-begäranden för formuläret /api/test2/xyz,/api/test2/123/api/test2/{any string} osv.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • Åtgärden GetIntProduct innehåller mallen "int/{id:int}" . Delen :int av mallen begränsar id vägvärdena till strängar som kan konverteras till ett heltal. En GET-begäran till /api/test2/int/abc:
    • Matchar inte den här åtgärden.
    • Returnerar felet 404 Hittades inte .
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • Åtgärden GetInt2Product innehåller {id} i mallen, men begränsar id inte till värden som kan konverteras till ett heltal. En GET-begäran till /api/test2/int2/abc:
    • Passar denna rutt.
    • Modellbindningen kan inte konverteras abc till ett heltal. Parametern id för metoden är heltal.
    • Returnerar en 400 felaktig begäran eftersom modellbindningen inte kunde konverteras abc till ett heltal.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Attributroutning kan använda HttpMethodAttribute attribut som HttpPostAttribute, HttpPutAttributeoch HttpDeleteAttribute. Alla HTTP-verbsattribut accepterar en vägmall. I följande exempel visas två åtgärder som matchar samma vägmall:

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

Använda URL-sökvägen /products3:

  • Åtgärden MyProductsController.ListProducts körs när HTTP-verbet är GET.
  • Åtgärden MyProductsController.CreateProduct körs när HTTP-verbet är POST.

När du skapar ett REST API är det ovanligt att du behöver använda [Route(...)] en åtgärdsmetod eftersom åtgärden accepterar alla HTTP-metoder. Det är bättre att använda det mer specifika HTTP-verbattributet för att vara exakt om vad ditt API stöder. Klienter för REST API:er förväntas veta vilka sökvägar och HTTP-verb som mappar till specifika logiska åtgärder.

REST API:er bör använda attributroutning för att modellera appens funktioner som en uppsättning resurser där åtgärder representeras av HTTP-verb. Det innebär att många åtgärder, till exempel GET och POST på samma logiska resurs, använder samma URL. Attributroutning ger en kontrollnivå som krävs för att noggrant utforma ett API:s offentliga slutpunktslayout.

Eftersom en attributväg gäller för en specifik åtgärd är det enkelt att göra parametrar som krävs som en del av definitionen av routningsmallen. I följande exempel id krävs som en del av URL-sökvägen:

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

Åtgärden Products2ApiController.GetProduct(int) :

  • Körs med URL-sökväg som /products2/3
  • Körs inte med URL-sökvägen /products2.

Attributet [Förbrukar] tillåter en åtgärd för att begränsa innehållstyperna för begäranden som stöds. Mer information finns i Definiera innehållstyper för begäranden som stöds med attributet Förbrukar.

Se Routning för en fullständig beskrivning av routningsmallar och relaterade alternativ.

Mer information om [ApiController]finns i ApiController-attribut.

Routningsnamn

Följande kod definierar vägnamnet Products_List:

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

Routningsnamn kan användas för att generera en URL baserat på en specifik väg. Routningsnamn:

  • Ha ingen inverkan på url-matchningsbeteendet för routning.
  • Används endast för URL-generering.

Routningsnamn måste vara unika för hela programmet.

Kontrastera föregående kod med den konventionella standardvägen, som definierar parametern id som valfri ({id?}). Möjligheten att exakt ange API:er har fördelar, till exempel att tillåta /products och /products/5 skickas till olika åtgärder.

Kombinera attributvägar

För att göra attributdirigering mindre repetitiv kombineras routningsattributen på kontrollanten med routningsattribut för de enskilda åtgärderna. Alla vägmallar som definierats på kontrollern läggs till före vägmallarna på åtgärderna. Om du placerar ett routningsattribut på kontrollanten används attributdirigering för alla åtgärder i kontrollanten.

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

I föregående exempel:

  • URL-sökvägen /products kan matcha ProductsApi.ListProducts
  • URL-sökvägen /products/5 kan matcha ProductsApi.GetProduct(int).

Båda dessa åtgärder matchar bara HTTP GET eftersom de har markerats med attributet [HttpGet] .

Routningsmallar som tillämpas på en åtgärd som börjar med / eller ~/ kombineras inte med routningsmallar som tillämpas på kontrollern. Följande exempel matchar en uppsättning URL-sökvägar som liknar standardvägen.

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

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

I följande tabell förklaras attributen [Route] i föregående kod:

Attribute Kombinerar med [Route("Home")] Definierar routningsmall
[Route("")] Yes "Home"
[Route("Index")] Yes "Home/Index"
[Route("/")] No ""
[Route("About")] Yes "Home/About"

Ordning för attributrutter

Routning skapar ett träd och matchar alla slutpunkter samtidigt:

  • Routningsposterna beter sig som om de vore placerade i en idealisk ordning.
  • De mest specifika rutterna har möjlighet att köras före de mer allmänna rutterna.

Till exempel är en attributväg som blog/search/{topic} är mer specifik än en attributväg som blog/{*article}. Vägen blog/search/{topic} har högre prioritet, som standard, eftersom den är mer specifik. Med hjälp av konventionell routning ansvarar utvecklaren för att placera vägar i önskad ordning.

Attributvägar kan konfigurera en ordning med hjälp av egenskapen Order. Alla routningsattribut som tillhandahålls av ramverket är Order . Vägar bearbetas enligt en stigande typ av Order egenskapen. Standardordningen är 0. Ange en rutt med Order = -1 innan rutter som inte anger någon ordning. Ställ in en rutt genom Order = 1 som körs efter standardruttordning.

Undvik beroende på Order. Om en apps URL-utrymme kräver explicita ordervärden för att dirigeras korrekt är det sannolikt förvirrande även för klienter. I allmänhet väljer attributroutning rätt väg med URL-matchning. Om standardordningen som används för URL-generering inte fungerar är det vanligtvis enklare att använda ett routningsnamn som åsidosättning än att tillämpa Order egenskapen.

Överväg följande två kontrollanter som båda definierar routningsmatchningen /home:

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

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

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

Om du begär /home med föregående kod genereras ett undantag som liknar följande:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

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

Om du lägger Order till något av routningsattributen löses tvetydigheten:

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

Med den föregående koden kör /home slutpunkten HomeController.Index. För att komma till MyDemoController.MyIndex, begär /home/MyIndex. Note:

  • Föregående kod är ett exempel eller dålig routningsdesign. Den användes för att illustrera egenskapen Order .
  • Egenskapen Order löser bara tvetydigheten, den mallen kan inte matchas. Det vore bättre att ta bort mallen [Route("Home")] .

Se Razor Sidors routnings- och appkonventioner: Routningsordning för information om routningsordning med Razor Sidor.

I vissa fall returneras ett HTTP 500-fel med tvetydiga vägar. Använd loggning för att se vilka ändpunkter som orsakade AmbiguousMatchException.

Ersättning av tokens i vägmallar [kontroller], [aktion], [område]

För enkelhetens skull stöder attributvägar tokenbyte genom att omsluta en token i hakparenteser ([, ]). Token , [action]och [area] ersätts med värdena för åtgärdsnamnet[controller], områdesnamnet och kontrollantnamnet från åtgärden där vägen definieras:

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

I koden ovan:

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

Tokenbyte sker som det sista steget för att skapa attributvägarna. Föregående exempel beter sig på samma sätt som följande kod:

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

Om du läser detta på ett annat språk än engelska kan du meddela oss i det här GitHub-diskussionsproblemet om du vill se kodkommentarna på ditt modersmål.

Attributvägar kan också kombineras med arv. Detta är kraftfullt kombinerat med tokenbyte. Tokenbyte gäller även för routningsnamn som definierats av attributvägar. [Route("[controller]/[action]", Name="[controller]_[action]")]genererar ett unikt vägnamn för varje åtgärd:

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

Om du vill matcha den literala tokenbyteslimiten [ eller ], tar du bort den genom att upprepa tecknet ([[ eller ]]).

Använda en parametertransformator för att anpassa tokenbyte

Tokenbyte kan anpassas med hjälp av en parametertransformator. En parametertransformator implementerar IOutboundParameterTransformer och transformerar värdet för parametrar. En anpassad SlugifyParameterTransformer parametertransformator ändrar till exempel routningsvärdet SubscriptionManagement till 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();
    }
}

RouteTokenTransformerConvention Är en konvention för programmodell som:

  • Tillämpar en parametertransformator på alla attributvägar i ett program.
  • Anpassar attributvägens tokenvärden när de ersätts.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

ListAll Föregående metod matchar /subscription-management/list-all.

RouteTokenTransformerConvention är registrerat som ett alternativ i ConfigureServices.

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

Se MDN-webbdokument på Slug för definitionen av Slug.

Warning

När du använder System.Text.RegularExpressions för att bearbeta ej betrodda indata skickar du en timeout. En obehörig användare kan ange indata för att RegularExpressions orsaka en Denial-of-Service-attack. ASP.NET Core Frameworks API som använder RegularExpressions har en timeout.

Flera attributvägar

Attributroutning stöder definition av flera vägar som når samma åtgärd. Den vanligaste användningen av detta är att efterlikna beteendet för den vanliga konventionella vägen enligt följande exempel:

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

Om du placerar flera routningsattribut på kontrollanten innebär det att var och en kombineras med vart och ett av routningsattributen på åtgärdsmetoderna:

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

Alla villkor för HTTP-verbvägen implementerar IActionConstraint.

När flera routattribut som implementerar IActionConstraint placeras till en åtgärd:

  • Varje åtgärdsbegränsning kombineras med routningsmallen som tillämpas på kontrollanten.
[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();
    }
}

Om du använder flera vägar för åtgärder kan det verka användbart och kraftfullt. Det är bättre att hålla appens URL-utrymme grundläggande och väldefinierat. Använd flera vägar för åtgärder endast där det behövs, till exempel för att stödja befintliga kunder.

Ange valfria parametrar för attributväg, standardvärden och begränsningar

Attributvägar stöder samma infogade syntax som konventionella vägar för att ange valfria parametrar, standardvärden och begränsningar.

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

I föregående kod [HttpPost("product14/{id:int}")] tillämpar du en routningsbegränsning. Åtgärden Products14Controller.ShowProduct matchas endast av URL-sökvägar som /product14/3. Routningsmalldelen {id:int} begränsar segmentet till enbart heltal.

En detaljerad beskrivning av routningsmallssyntaxen finns i Referens för routningsmall .

Anpassade routningsattribut med IRouteTemplateProvider

Alla routningsattribut implementerar IRouteTemplateProvider. ASP.NET Core körmiljö:

  • Söker efter attribut på kontrollantklasser och åtgärdsmetoder när appen startar.
  • Använder attributen som implementerar IRouteTemplateProvider för att skapa den initiala uppsättningen rutter.

Implementera IRouteTemplateProvider för att definiera anpassade routningsattribut. Varje IRouteTemplateProvider gör att du kan definiera en enskild rutt med en anpassad ruttmall, ordning och namn:

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

Get Föregående metod returnerar Order = 2, Template = api/MyTestApi.

Använda programmodell för att anpassa attributvägar

Programmodellen:

  • En objektmodell skapas vid start.
  • Innehåller alla metadata som används av ASP.NET Core för att dirigera och köra åtgärderna i en app.

Programmodellen innehåller alla data som samlas in från routningsattribut. Data från routningsattribut tillhandahålls av implementeringen IRouteTemplateProvider . Conventions:

  • Kan skrivas för att ändra programmodellen för att anpassa hur routning fungerar.
  • När appen startar läses de.

Det här avsnittet visar ett grundläggande exempel på hur du anpassar routning med hjälp av programmodellen. Följande kod gör vägarna ungefär i linje med projektets mappstruktur.

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

Följande kod förhindrar att konventionen namespace tillämpas på kontroller som är attributroutade:

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

Följande styrenhet använder NamespaceRoutingConventiontill exempel inte :

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

NamespaceRoutingConvention.Apply-metoden:

  • Gör ingenting om kontrollen är attribut-routerad.
  • Anger kontrollantmallen baserat på namespace, med basen namespace borttagen.

NamespaceRoutingConvention Kan användas i 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.

Tänk till exempel på följande kontrollant:

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

I koden ovan:

  • Basen namespace är My.Application.
  • Det fullständiga namnet på den föregående kontrollanten är My.Application.Admin.Controllers.UsersController.
  • Anger NamespaceRoutingConvention kontrollantmallen till Admin/Controllers/Users/[action]/{id?.

NamespaceRoutingConvention Kan också användas som ett attribut på en kontrollant:

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

Blandad routning: Attributroutning jämfört med konventionell routning

ASP.NET Core-appar kan blanda användningen av konventionell routning och attributroutning. Det är vanligt att använda konventionella vägar för kontrollanter som betjänar HTML-sidor för webbläsare och attributroutning för kontrollanter som betjänar REST API:er.

Åtgärder dirigeras antingen konventionellt eller dirigeras med attribut. Om du placerar en väg på kontrollanten eller åtgärden dirigeras attributet. Åtgärder som definierar attributvägar kan inte nås via konventionella vägar och vice versa. Varje routningsattribut på kontrollern gör alla åtgärder i kontrollern dirigerade.

Attributroutning och konventionell routning använder samma routningsmotor.

URL-generering och omgivande värden

Appar kan använda genereringsfunktioner för routnings-URL för att generera URL-länkar till åtgärder. Att generera URL:er eliminerar hårdkodnings-URL:er, vilket gör koden mer robust och underhållsbar. Det här avsnittet fokuserar på url-genereringsfunktionerna som tillhandahålls av MVC och omfattar endast grunderna för hur URL-generering fungerar. Se Routning för en detaljerad beskrivning av URL-generering.

Gränssnittet IUrlHelper är det underliggande elementet i infrastrukturen mellan MVC och routning för URL-generering. En instans av IUrlHelper är tillgänglig via Url egenskapen i kontrollanter, vyer och visningskomponenter.

I följande exempel IUrlHelper används gränssnittet via egenskapen Controller.Url för att generera en URL till en annan åtgärd.

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

Om appen använder den vanliga standardvägen är värdet för variabeln url URL-sökvägssträngen /UrlGeneration/Destination. Den här URL-sökvägen skapas genom routning genom att kombinera:

  • Routningsvärdena från den aktuella begäran, som kallas för omgivande värden.
  • Värdena som skickas till Url.Action och de värden som ersätter dessa i routmallen:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Varje ruttparameter i vägmallen får sitt värde ersatt av att matcha namn med värden och omgivande värden. En vägparameter som inte har något värde kan:

  • Använd ett standardvärde om det har ett.
  • Hoppas över om det är valfritt. Till exempel id från vägmallen {controller}/{action}/{id?}.

URL-genereringen misslyckas om någon obligatorisk vägparameter inte har något motsvarande värde. Om URL-genereringen misslyckas för en väg provas nästa väg tills alla vägar har provats eller en matchning hittas.

Föregående exempel på Url.Action förutsätter konventionell routning. URL-generering fungerar på samma sätt med attributroutning, även om begreppen skiljer sig. Med konventionell routning:

  • Vägvärdena används för att expandera en mall.
  • Vägvärdena för controller och action visas vanligtvis i mallen. Detta fungerar eftersom URL:erna som matchas av routning följer en konvention.

I följande exempel används attributroutning:

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

Åtgärden Source i föregående kod genererar custom/url/to/destination.

LinkGenerator lades till i ASP.NET Core 3.0 som ett alternativ till IUrlHelper. LinkGenerator erbjuder liknande men mer flexibla funktioner. Varje metod på IUrlHelper har också en motsvarande uppsättning metoder LinkGenerator .

Generera URL:er efter åtgärdsnamn

Url.Action, LinkGenerator.GetPathByAction och alla relaterade överlagringar är alla utformade för att generera målslutpunkten genom att ange ett kontrollantnamn och åtgärdsnamn.

När du använder Url.Action, tillhandahålls de aktuella ruttvärdena för controller och action av körningstiden.

  • Värdet för controller och action är en del av både omgivande värden och värden. Metoden Url.Action använder alltid de aktuella värdena action för och controller och genererar en URL-sökväg som dirigerar till den aktuella åtgärden.

Routning försöker använda värdena i omgivande värden för att fylla i information som inte angavs när en URL genererades. Överväg en väg som {a}/{b}/{c}/{d} med omgivande värden { a = Alice, b = Bob, c = Carol, d = David }:

  • Routning har tillräckligt med information för att generera en URL utan ytterligare värden.
  • Routning har tillräckligt med information eftersom alla vägparametrar har ett värde.

Om värdet { d = Donovan } läggs till:

  • Värdet { d = David } ignoreras.
  • Den genererade URL-sökvägen är Alice/Bob/Carol/Donovan.

Varning! URL-sökvägar är hierarkiska. Om värdet { c = Cheryl } läggs till i föregående exempel:

  • Båda värdena { c = Carol, d = David } ignoreras.
  • Det finns inte längre något värde för d och URL-genereringen misslyckas.
  • Önskade värden för c och d måste anges för att generera en URL.

Du kan förvänta dig att stöta på det här problemet med standardvägen {controller}/{action}/{id?}. Det här problemet är sällsynt i praktiken eftersom Url.Action alltid uttryckligen anger ett controller och action -värde.

Flera överlagringar av URL.Action använder ett vägvärdesobjekt för att ange värden för andra vägparametrar än controller och action. Objektet för vägvärden används ofta med id. Till exempel Url.Action("Buy", "Products", new { id = 17 }). Objektet för ruttvärden

  • Enligt konvention är det vanligtvis ett objekt av anonym typ.
  • Kan vara en IDictionary<> eller en POCO).

Eventuella ytterligare vägvärden som inte matchar routningsparametrar placeras i frågesträngen.

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

Föregående kod genererar /Products/Buy/17?color=red.

Följande kod genererar en absolut 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);
}

Om du vill skapa en absolut URL använder du något av följande:

  • En överbelastning som accepterar en protocol. Till exempel föregående kod.
  • LinkGenerator.GetUriByAction, som genererar absoluta URI:er som standard.

Generera URL:er efter väg

Föregående kod visade hur du genererade en URL genom att skicka in kontrollanten och åtgärdsnamnet. IUrlHelper innehåller också url.RouteUrl-serien med metoder. Dessa metoder liknar Url.Action, men de kopierar inte de aktuella värdena action för och controller till vägvärdena. Den vanligaste användningen av Url.RouteUrl:

  • Anger ett vägnamn för att generera URL:en.
  • I allmänhet anger inte en kontrollant eller åtgärdsnamn.
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();
    }

Följande Razor fil genererar en HTML-länk till Destination_Route:

<h1>Test Links</h1>

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

Generera URL:er i HTML och Razor

IHtmlHelper HtmlHelper innehåller metoderna Html.BeginForm och Html.ActionLink för att generera <form><a> respektive element. Dessa metoder använder metoden Url.Action för att generera en URL och de accepterar liknande argument. Följeslagare Url.RouteUrl för HtmlHelper är Html.BeginRouteForm och Html.RouteLink som har liknande funktioner.

TagHelpers genererar URL:er via form TagHelper och <a> TagHelper. Båda dessa använder IUrlHelper för deras implementering. För mer information, se Tag Helpers i formulär.

I vyer, IUrlHelper finns tillgänglig via Url den egenskapen för alla ad hoc-URL-genereringar som inte omfattas av ovanstående.

URL-generering i åtgärdsresultat

De föregående exemplen visade hur man använder IUrlHelper i en kontroller. Det vanligaste syftet med en kontroller är att generera en URL som en del av ett åtgärdsresultat.

Basklasserna ControllerBase och Controller tillhandahåller bekvämlighetsmetoder för åtgärdsresultat som refererar till en annan åtgärd. En vanlig användning är att omdirigera efter att ha accepterat användarindata:

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

Metoder för åtgärdsresultat som RedirectToAction och CreatedAtAction följer ett liknande mönster som metoderna hos IUrlHelper.

Specialfall för dedikerade konventionella vägar

Konventionell routning kan använda en särskild typ av vägdefinition som kallas en dedikerad konventionell väg. I följande exempel är den väg som heter blog en dedikerad konventionell väg:

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

Med hjälp av de föregående ruttdefinitionerna genererar Url.Action("Index", "Home") URL-sökvägen / med rutt default, men varför? Du kan gissa att vägvärdena { controller = Home, action = Index } räcker för att generera en URL med , blogoch resultatet blir /blog?action=Index&controller=Home.

Dedikerade konventionella vägar förlitar sig på ett särskilt beteende med standardvärden som inte har en motsvarande vägparameter som förhindrar att vägen blir för girig med URL-generering. I det här fallet är { controller = Blog, action = Article }standardvärdena , och varken controller eller action visas som en vägparameter. När routningen utför URL-generering måste de angivna värdena matcha standardvärdena. URL-genereringen med hjälp av blog misslyckas eftersom värdena { controller = Home, action = Index } inte matchar { controller = Blog, action = Article }. Routning faller sedan tillbaka för att prova default, vilket lyckas.

Areas

Områden är en MVC-funktion som används för att organisera relaterade funktioner i en grupp som en separat:

  • Routningsnamnområde för kontrollantåtgärder.
  • Mappstruktur för vyer.

Med hjälp av områden kan en app ha flera kontrollanter med samma namn, så länge de har olika områden. Med hjälp av områden skapas en hierarki för routning genom att lägga till en annan routningsparameter, area till controller och action. I det här avsnittet beskrivs hur routning interagerar med områden. Mer information om hur områden används med vyer finns i Områden .

I följande exempel konfigureras MVC att använda standardrutt för konventionell väg och en area rutt för en area som heter Blog.

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

I föregående kod MapAreaControllerRoute anropas för att skapa "blog_route". Den andra parametern, "Blog", är områdesnamnet.

När du matchar en URL-sökväg som /Manage/Users/AddUser"blog_route" genererar vägen vägvärdena { area = Blog, controller = Users, action = AddUser }. Routningsvärdet area genereras av ett standardvärde för area. Den väg som skapas av MapAreaControllerRoute motsvarar följande:

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 skapar en väg med både ett standardvärde och en begränsning för area att använda det angivna områdesnamnet, i det här fallet Blog. Standardvärdet säkerställer att vägen alltid genererar { area = Blog, ... }, villkoret kräver värdet { area = Blog, ... } för URL-generering.

Konventionell routning är orderberoende. I allmänhet bör vägar med områden placeras tidigare eftersom de är mer specifika än vägar utan område.

Med hjälp av föregående exempel matchar vägvärdena { area = Blog, controller = Users, action = AddUser } följande åtgärd:

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

Attributet [Area] är det som anger en kontrollant som en del av ett område. Den här styrenheten finns i området Blog . Kontrollanter utan attribut [Area] är inte medlemmar i något område och matchar inte när area routningsvärdet tillhandahålls av routning. I följande exempel kan endast den första kontrollanten i listan matcha vägvärdena { area = Blog, controller = Users, action = AddUser }.

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

Namnområdet för varje kontrollant visas här för fullständighet. Om de föregående kontrollanterna använde samma namnområde genereras ett kompilatorfel. Klassnamnområden har ingen effekt på MVC:s routning.

De två första kontrollanterna är medlemmar i områden och matchar bara när deras respektive områdesnamn anges av area vägvärdet. Den tredje styrenheten är inte medlem i något område och kan bara matcha när inget värde för area tillhandahålls av routning.

När det gäller att matcha inget värde är frånvaron av area värdet densamma som om värdet för area var null eller den tomma strängen.

När du kör en åtgärd i ett område är routningsvärdet för area tillgängligt som ett omgivande värde för routning som ska användas för URL-generering. Det innebär att som standard fungerar områden som är klibbiga för URL-generering, vilket visas i följande exempel.

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

Följande kod genererar en URL till /Zebra/Users/AddUser:

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

Åtgärdsdefinition

Offentliga metoder på en kontrollant, förutom de med attributet NonAction , är åtgärder.

Exempelkod

Felsökningsdiagnostik

För detaljerade routningsdiagnostikutdata anger du Logging:LogLevel:Microsoft till Debug. I utvecklingsmiljön anger du loggnivån i appsettings.Development.json:

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