Routing ad azioni del controller in ASP.NET Core

Di Ryan Nowak, Kirk Larkin e Rick Anderson

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

ASP.NET Controller di base usano il middleware di routing per trovare le corrispondenze con gli URL delle richieste in ingresso ed eseguirne il mapping alle azioni. Modelli di route:

  • Vengono definiti all'avvio in Program.cs o in attributi.
  • Descrivere in che modo i percorsi URL vengono confrontati con le azioni.
  • Vengono usati per generare URL per i collegamenti. I collegamenti generati vengono in genere restituiti nelle risposte.

Le azioni sono instradate convenzionalmente o instradate con attributi. L'inserimento di una route sul controller o sull'azione rende l'attributo indirizzato. Per altre informazioni, vedere Routing misto.

Questo documento:

  • Illustra le interazioni tra MVC e routing:
    • Come le app MVC tipiche usano le funzionalità di routing.
    • Copre entrambi:
    • Vedere Routing per informazioni dettagliate sul routing avanzato.
  • Fa riferimento al sistema di routing predefinito denominato routing degli endpoint. È possibile usare i controller con la versione precedente del routing a scopo di compatibilità. Per istruzioni, vedere la guida alla migrazione 2.2-3.0.

Configurare la route convenzionale

Il modello ASP.NET Core MVC genera codice di routing convenzionale simile al seguente:

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 viene usato per creare una singola route. La singola route è denominata default route. La maggior parte delle app con controller e visualizzazioni usa un modello di route simile alla default route. REST Le API devono usare il routing degli attributi.

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

Modello di route "{controller=Home}/{action=Index}/{id?}":

  • Corrisponde a un percorso URL, ad esempio /Products/Details/5

  • Estrae i valori { controller = Products, action = Details, id = 5 } di route tramite tokenizzazione del percorso. L'estrazione dei valori di route genera una corrispondenza se l'app ha un controller denominato ProductsController e un'azione Details :

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

    MyDisplayRouteInfo viene fornito dal pacchetto NuGet Rick.Docs.Samples.RouteInfo e visualizza le informazioni sulla route.

  • /Products/Details/5 model associa il valore di id = 5 per impostare il id parametro su 5. Per altri dettagli, vedere Associazione di modelli.

  • {controller=Home} definisce Home come valore predefinito controller.

  • {action=Index} definisce Index come valore predefinito action.

  • Il ? carattere in {id?} definisce id come facoltativo.

    • I parametri di route predefiniti e facoltativi non devono necessariamente essere presenti nel percorso URL per trovare una corrispondenza. Vedere Riferimento per i modelli di route per una descrizione dettagliata della sintassi del modello di route.
  • Trova la corrispondenza con il percorso /URL .

  • Produce i valori { controller = Home, action = Index }di route .

I valori per controller e action usano i valori predefiniti. id non produce un valore perché non esiste alcun segmento corrispondente nel percorso URL. / corrisponde solo se esiste un'azione HomeController e Index :

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

Usando la definizione del controller e il modello di route precedenti, l'azione HomeController.Index viene eseguita per i percorsi URL seguenti:

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

Il percorso / URL usa i controller e Index l'azione predefiniti Home del modello di route. Il percorso /Home URL usa l'azione predefinita Index del modello di route.

Il metodo pratico MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Sostituisce:

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

Importante

Il routing viene configurato usando il UseRouting middleware e UseEndpoints . Per usare i controller:

Le app in genere non devono chiamare UseRouting o UseEndpoints. WebApplicationBuilder configura una pipeline middleware che esegue il wrapping del middleware aggiunto in Program.cs con UseRouting e UseEndpoints. Per altre informazioni, vedere Routing in ASP.NET Core.

Routing convenzionale

Il routing convenzionale viene usato con controller e visualizzazioni. La route predefinita (default):

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

Il precedente è un esempio di route convenzionale. Si chiama routing convenzionale perché stabilisce una convenzione per i percorsi URL:

  • Il primo segmento di percorso, {controller=Home}, esegue il mapping al nome del controller.
  • Il secondo segmento, {action=Index}, esegue il mapping al nome dell'azione.
  • Il terzo segmento viene {id?} usato per un oggetto facoltativo id. l'oggetto ? in {id?} lo rende facoltativo. id viene usato per eseguire il mapping a un'entità del modello.

Usando questa default route, il percorso URL:

  • /Products/List esegue il mapping all'azione ProductsController.List .
  • /Blog/Article/17 esegue il mapping a BlogController.Article e in genere il modello associa il id parametro a 17.

Questo mapping:

  • Si basa solo sui nomi di controller e azione.
  • Non si basa su spazi dei nomi, percorsi dei file di origine o parametri del metodo.

L'uso del routing convenzionale con la route predefinita consente di creare l'app senza dover creare un nuovo modello di URL per ogni azione. Per un'app con azioni di stile CRUD , avere coerenza per gli URL tra i controller:

  • Semplifica il codice.
  • Rende l'interfaccia utente più prevedibile.

Avviso

Nel id codice precedente viene definito come facoltativo dal modello di route. Le azioni possono essere eseguite senza l'ID facoltativo fornito come parte dell'URL. In genere, quando id viene omesso dall'URL:

  • id è impostato su per associazione di 0 modelli.
  • Nessuna entità viene trovata nel database corrispondente a id == 0.

Il routing degli attributi fornisce un controllo granulare per rendere l'ID necessario per alcune azioni e non per altri. Per convenzione, la documentazione include parametri facoltativi come id quando è probabile che vengano visualizzati nell'utilizzo corretto.

La maggior parte delle app dovrebbe scegliere uno schema di routing semplice e descrittivo in modo che gli URL siano leggibili e significativi. La route convenzionale predefinita {controller=Home}/{action=Index}/{id?}:

  • Supporta uno schema di routing semplice e descrittivo.
  • È un punto iniziale utile per le app basate su interfaccia utente.
  • È l'unico modello di route necessario per molte app dell'interfaccia utente Web. Per le app dell'interfaccia utente Web di grandi dimensioni, un'altra route che usa Aree è spesso tutto ciò che serve.

MapControllerRoute e MapAreaRoute :

  • Assegnare automaticamente un valore di ordine agli endpoint in base all'ordine in cui vengono richiamati.

Endpoint routing in ASP.NET Core:

  • Non ha un concetto di route.
  • Non fornisce garanzie di ordinamento per l'esecuzione dell'estendibilità, tutti gli endpoint vengono elaborati contemporaneamente.

Abilitare la registrazione per verificare in che modo le implementazioni del routing predefinite, ad esempio Route, corrispondono alle richieste.

Il routing degli attributi è illustrato più avanti in questo documento.

Più route convenzionali

È possibile configurare più route convenzionali aggiungendo altre chiamate a MapControllerRoute e MapAreaControllerRoute. In questo modo è possibile definire più convenzioni o aggiungere route convenzionali dedicate a un'azione specifica, ad esempio:

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

La blog route nel codice precedente è una route convenzionale dedicata. Si chiama route convenzionale dedicata perché:

Poiché controller e action non vengono visualizzati nel modello "blog/{*article}" di route come parametri:

  • Possono avere solo i valori { controller = "Blog", action = "Article" }predefiniti.
  • Questa route esegue sempre il mapping all'azione BlogController.Article.

/Blog, /Blog/Articlee /Blog/{any-string} sono gli unici percorsi URL che corrispondono alla route del blog.

L'esempio precedente:

  • blog la route ha una priorità più alta per le corrispondenze rispetto alla default route perché viene aggiunta per prima.
  • Esempio di routing dello stile Slug in cui è tipico avere un nome di articolo come parte dell'URL.

Avviso

In ASP.NET Core il routing non è:

  • Definire un concetto denominato route. UseRouting aggiunge la corrispondenza della route alla pipeline middleware. Il UseRouting middleware esamina il set di endpoint definiti nell'app e seleziona la corrispondenza dell'endpoint migliore in base alla richiesta.
  • Fornire garanzie sull'ordine di esecuzione dell'estendibilità, ad esempio IRouteConstraint o IActionConstraint.

Vedere Routing per materiale di riferimento sul routing.

Ordine di routing convenzionale

Il routing convenzionale corrisponde solo a una combinazione di azioni e controller definiti dall'app. Questo è progettato per semplificare i casi in cui le route convenzionali si sovrappongono. L'aggiunta di route tramite MapControllerRoute, MapDefaultControllerRoutee MapAreaControllerRoute assegna automaticamente un valore di ordine agli endpoint in base all'ordine in cui vengono richiamate. Le corrispondenze di una route visualizzata in precedenza hanno una priorità più alta. Il routing convenzionale dipende dall'ordinamento. In generale, le rotte con aree devono essere posizionate in precedenza perché sono più specifiche di quelle senza un'area. Le route convenzionali dedicate con parametri di route catch-all come {*article} possono creare una route troppo greedy, ovvero corrisponde agli URL che si intende associare ad altre route. Inserire le route greedy più avanti nella tabella di route per impedire corrispondenze greedy.

Avviso

Un parametro catch-all può corrispondere erroneamente alle route a causa di un bug nel routing. Le app interessate da questo bug presentano le caratteristiche seguenti:

  • Un itinerario catch-all, ad esempio {**slug}"
  • La route catch-all non riesce a trovare una corrispondenza con le richieste che deve corrispondere.
  • La rimozione di altre route rende la route catch-all iniziare a funzionare.

Vedere Bug di GitHub 18677 e 16579 per casi di esempio che hanno raggiunto questo bug.

Una correzione di consenso esplicito per questo bug è contenuta in .NET Core 3.1.301 SDK e versioni successive. Il codice seguente imposta un commutatore interno che corregge questo bug:

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

Risoluzione di azioni ambigue

Quando due endpoint corrispondono tramite routing, il routing deve eseguire una delle operazioni seguenti:

  • Scegliere il candidato migliore.
  • Generazione di un'eccezione.

Ad esempio:

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

Il controller precedente definisce due azioni che corrispondono:

  • Percorso URL /Products33/Edit/17
  • Indirizzare i dati { controller = Products33, action = Edit, id = 17 }.

Si tratta di un modello tipico per i controller MVC:

  • Edit(int) visualizza un modulo per modificare un prodotto.
  • Edit(int, Product) elabora il modulo pubblicato.

Per risolvere la route corretta:

  • Edit(int, Product) viene selezionato quando la richiesta è un http POST.
  • Edit(int) viene selezionato quando il verbo HTTP è qualsiasi altro elemento. Edit(int) viene in genere chiamato tramite GET.

L'oggetto HttpPostAttribute, [HttpPost]viene fornito al routing in modo che possa scegliere in base al metodo HTTP della richiesta. rende HttpPostAttributeEdit(int, Product) una corrispondenza migliore rispetto a Edit(int).

È importante comprendere il ruolo degli attributi, ad esempio HttpPostAttribute. Gli attributi simili sono definiti per altri verbi HTTP. Nel routing convenzionale, è comune che le azioni usino lo stesso nome di azione quando fanno parte di un modulo di presentazione, inviare il flusso di lavoro del modulo. Ad esempio, vedere Esaminare i due metodi di azione Edit.

Se il routing non è in grado di scegliere un candidato migliore, viene generata un'eccezione AmbiguousMatchException , elencando più endpoint corrispondenti.

Nomi di route convenzionali

Le stringhe "blog" e "default" negli esempi seguenti sono nomi di route convenzionali:

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

I nomi delle route assegnano alla route un nome logico. La route denominata può essere usata per la generazione di URL. L'uso di una route denominata semplifica la creazione di URL quando l'ordinamento delle route potrebbe rendere complessa la generazione di URL. I nomi di route devono essere univoci a livello di applicazione.

Nomi di route:

  • Non avere alcun impatto sulla corrispondenza dell'URL o sulla gestione delle richieste.
  • Vengono usati solo per la generazione di URL.

Il concetto di nome della route è rappresentato nel routing come IEndpointNameMetadata. I termini nome della route e nome dell'endpoint:

  • Sono intercambiabili.
  • Quello usato nella documentazione e nel codice dipende dall'API descritta.

Routing degli attributi per REST le API

REST Le API devono usare il routing degli attributi per modellare la funzionalità dell'app come set di risorse in cui le operazioni sono rappresentate da verbi HTTP.

Il routing con attributi usa un set di attributi per eseguire il mapping delle azioni direttamente ai modelli di route. Il codice seguente è tipico per un'API REST e viene usato nell'esempio seguente:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Nel codice MapControllers precedente viene chiamato per eseguire il mapping dei controller indirizzati con attributi.

Nell'esempio seguente :

  • HomeController corrisponde a un set di URL simili a quello che corrisponde alla route {controller=Home}/{action=Index}/{id?} convenzionale predefinita.
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);
    }
}

L'azione HomeController.Index viene eseguita per uno dei percorsi /URL , , /Home/Index/Homeo /Home/Index/3.

In questo esempio viene evidenziata una differenza di programmazione chiave tra il routing degli attributi e il routing convenzionale. Il routing degli attributi richiede più input per specificare una route. La route predefinita convenzionale gestisce le route in modo più conciso. Tuttavia, il routing degli attributi consente e richiede un controllo preciso dei modelli di route applicabili a ogni azione.

Con il routing degli attributi, i nomi dei controller e delle azioni non fanno parte della corrispondenza dell'azione, a meno che non venga usata la sostituzione del token. L'esempio seguente corrisponde agli stessi URL dell'esempio precedente:

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

Il codice seguente usa la sostituzione dei token per action e 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();
    }
}

Il codice seguente si applica [Route("[controller]/[action]")] al controller:

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

Nel codice precedente, i Index modelli di metodo devono anteporre / o ~/ ai modelli di route. I modelli di route applicati a un'azione che iniziano con / o ~/ non vengono combinati con i modelli di route applicati al controller.

Per informazioni sulla selezione del modello di route, vedere Precedenza del modello di route.

Nomi riservati di routing

Le parole chiave seguenti sono nomi di parametri di route riservate quando si usano controller o Razor pagine:

  • action
  • area
  • controller
  • handler
  • page

L'uso page di come parametro di route con il routing degli attributi è un errore comune. In questo modo si verifica un comportamento incoerente e confuso con la generazione di URL.

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

I nomi dei parametri speciali vengono usati dalla generazione di URL per determinare se un'operazione di generazione url fa riferimento a una Razor pagina o a un controller.

Le parole chiave seguenti sono riservate nel contesto di una Razor visualizzazione o di una Razor pagina:

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

Queste parole chiave non devono essere usate per le generazioni di collegamenti, i parametri associati al modello o le proprietà di livello superiore.

Modelli di verbo HTTP

ASP.NET Core include i modelli di verbo HTTP seguenti:

Modelli di route

ASP.NET Core include i modelli di route seguenti:

  • Tutti i modelli di verbo HTTP sono modelli di route.
  • [Route]

Routing degli attributi con attributi verbi HTTP

Prendere in considerazione il controller seguente:

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

Nel codice precedente:

  • Ogni azione contiene l'attributo [HttpGet] , che vincola solo la corrispondenza alle richieste HTTP GET.
  • L'azione GetProduct include il "{id}" modello, quindi id viene aggiunto al modello nel "api/[controller]" controller. Il modello di metodi è "api/[controller]/{id}". Pertanto, questa azione corrisponde solo alle richieste GET per il modulo /api/test2/xyz,/api/test2/123/api/test2/{any string} e così via.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • L'azione GetIntProduct contiene il "int/{id:int}" modello. La :int parte del modello vincola i valori di id route alle stringhe che possono essere convertite in un numero intero. Richiesta GET a /api/test2/int/abc:
    • Non corrisponde a questa azione.
    • Restituisce un errore 404 Non trovato .
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • L'azione GetInt2Product contiene {id} nel modello, ma non limita id i valori che possono essere convertiti in un numero intero. Richiesta GET a /api/test2/int2/abc:
    • Corrisponde a questa route.
    • L'associazione di modelli non riesce a eseguire la conversione abc in un numero intero. Il id parametro del metodo è integer.
    • Restituisce una richiesta non valida 400 perché l'associazione di modelli non è riuscita a eseguire la conversione abc in un numero intero.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Il routing degli attributi può usare HttpMethodAttribute attributi come HttpPostAttribute, HttpPutAttributee HttpDeleteAttribute. Tutti gli attributi del verbo HTTP accettano un modello di route. L'esempio seguente mostra due azioni che corrispondono allo stesso modello di route:

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

Uso del percorso /products3URL :

  • L'azione MyProductsController.ListProducts viene eseguita quando il verbo HTTP è GET.
  • L'azione MyProductsController.CreateProduct viene eseguita quando il verbo HTTP è POST.

Quando si compila un'API REST , è raro che sia necessario usare [Route(...)] in un metodo di azione perché l'azione accetta tutti i metodi HTTP. È preferibile usare l'attributo verbo HTTP più specifico per essere preciso su ciò che supporta l'API. I client delle REST API devono conoscere i percorsi e i verbi HTTP mappati a operazioni logiche specifiche.

REST Le API devono usare il routing degli attributi per modellare la funzionalità dell'app come set di risorse in cui le operazioni sono rappresentate da verbi HTTP. Ciò significa che molte operazioni, ad esempio GET e POST nella stessa risorsa logica, usano lo stesso URL. Il routing degli attributi offre un livello di controllo necessario per progettare con attenzione il layout dell'endpoint pubblico di un'API.

Poiché una route con attributi si applica a un'azione specifica, è facile fare in modo che i parametri siano richiesti come parte della definizione del modello di route. Nell'esempio id seguente è necessario come parte del percorso URL:

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

Azione Products2ApiController.GetProduct(int) :

  • Viene eseguito con il percorso URL, ad esempio /products2/3
  • Non viene eseguito con il percorso /products2URL .

L'attributo [Consumes] consente a un'azione di limitare i tipi di contenuto della richiesta supportati. Per altre informazioni, vedere Definire i tipi di contenuto di richiesta supportati con l'attributo Consumes.

Vedere Routing per una descrizione completa dei modelli di route e delle opzioni correlate.

Per altre informazioni su [ApiController], vedere Attributo ApiController.

Nome route

Il codice seguente definisce un nome di route di Products_List:

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

I nomi di route possono essere usati per generare un URL in base a un percorso specifico. Nomi di route:

  • Non avere alcun impatto sul comportamento di corrispondenza dell'URL del routing.
  • Vengono usati solo per la generazione di URL.

I nomi delle route devono essere univoci a livello di applicazione.

Confrontare il codice precedente con la route predefinita convenzionale, che definisce il id parametro come facoltativo ({id?}). La possibilità di specificare con precisione le API presenta vantaggi, ad esempio consentire /products e /products/5 inviare a diverse azioni.

Combinazione di route degli attributi

Per rendere il routing con attributi meno ripetitivo, gli attributi di route del controller vengono combinati con gli attributi di route delle singole azioni. I modelli di route definiti per il controller vengono anteposti ai modelli di route delle azioni. Inserendo un attributo di route nel controller, tutte le azioni presenti nel controller useranno il routing con attributi.

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

Nell'esempio precedente:

  • Il percorso /products URL può corrispondere ProductsApi.ListProducts
  • Il percorso /products/5 URL può corrispondere a ProductsApi.GetProduct(int).

Entrambe queste azioni corrispondono solo a HTTP GET perché sono contrassegnate con l'attributo [HttpGet] .

I modelli di route applicati a un'azione che iniziano con / o ~/ non vengono combinati con i modelli di route applicati al controller. L'esempio seguente corrisponde a un set di percorsi URL simili alla route predefinita.

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

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

La tabella seguente illustra gli [Route] attributi nel codice precedente:

Attributo Combina con [Route("Home")] Definisce il modello di route
[Route("")] "Home"
[Route("Index")] "Home/Index"
[Route("/")] No ""
[Route("About")] "Home/About"

Ordine di route degli attributi

Il routing compila un albero e corrisponde a tutti gli endpoint contemporaneamente:

  • Le voci di route si comportano come se fossero inserite in un ordinamento ideale.
  • Le route più specifiche possono essere eseguite prima delle route più generali.

Ad esempio, una route di attributi come blog/search/{topic} è più specifica di una route di attributi come blog/{*article}. La blog/search/{topic} route ha priorità più alta, per impostazione predefinita, perché è più specifica. Usando il routing convenzionale, lo sviluppatore è responsabile dell'inserimento di route nell'ordine desiderato.

Le route degli attributi possono configurare un ordine usando la Order proprietà . Tutti gli attributi di route forniti dal framework includono Order . Le route vengono elaborate in base a un ordinamento crescente della proprietà Order. L'ordine predefinito è 0. Impostazione di una route tramite Order = -1 esecuzioni prima delle route che non impostano un ordine. Impostazione di una route tramite l'esecuzione Order = 1 dopo l'ordinamento predefinito della route.

Evitare di dipendere da Order. Se lo spazio URL di un'app richiede valori di ordine espliciti per il routing corretto, è probabile che si confonda anche con i client. In generale, il routing degli attributi seleziona la route corretta con la corrispondenza dell'URL. Se l'ordine predefinito usato per la generazione di URL non funziona, l'uso di un nome di route come override è in genere più semplice rispetto all'applicazione della Order proprietà .

Si considerino i due controller seguenti che definiscono entrambi la route corrispondente /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);
    }
}

La richiesta /home con il codice precedente genera un'eccezione simile alla seguente:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

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

L'aggiunta Order a uno degli attributi di route risolve l'ambiguità:

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

Con il codice precedente, /home esegue l'endpoint HomeController.Index . Per accedere a MyDemoController.MyIndex, richiedere /home/MyIndex. Nota:

  • Il codice precedente è un esempio o una progettazione di routing scadente. È stato usato per illustrare la Order proprietà .
  • La Order proprietà risolve solo l'ambiguità, che il modello non può corrispondere. Sarebbe preferibile rimuovere il [Route("Home")] modello.

Per informazioni sull'ordine di route con Pagine con Razor Pagine, vedi Razor Convenzioni di route e app.

In alcuni casi, viene restituito un errore HTTP 500 con route ambigue. Usare la registrazione per vedere quali endpoint hanno causato l'oggetto AmbiguousMatchException.

Sostituzione dei token nei modelli di route [controller], [azione], [area]

Per praticità, le route degli attributi supportano la sostituzione dei token racchiudendo un token tra parentesi quadre ([, ]). I token [action], [area]e [controller] vengono sostituiti con i valori del nome dell'azione, del nome dell'area e del nome del controller dall'azione in cui è definita la route:

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

Nel codice precedente:

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

La sostituzione dei token avviene come ultimo passaggio della creazione delle route con attributi. L'esempio precedente si comporta come il codice seguente:

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

Se lo si legge in una lingua diversa dall'inglese, segnalarlo in questo problema di discussione su GitHub se si vogliono visualizzare i commenti del codice nella lingua nativa.

Le route con attributi possono anche essere combinate con l'ereditarietà. Questo è potente combinato con la sostituzione dei token. La sostituzione dei token si applica anche ai nomi di route definiti dalle route con attributi. [Route("[controller]/[action]", Name="[controller]_[action]")]genera un nome di route univoco per ogni azione:

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

Per verificare la corrispondenza del delimitatore letterale della sostituzione di token [ o ], eseguirne l'escape ripetendo il carattere ([[ o ]]).

Usare un trasformatore di parametri per personalizzare la sostituzione dei token

La sostituzione dei token può essere personalizzata usando un trasformatore di parametri. Un trasformatore di parametri implementa IOutboundParameterTransformer e trasforma il valore dei parametri. Ad esempio, un trasformatore di parametro personalizzato SlugifyParameterTransformer modifica il valore della SubscriptionManagement route in 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 è una convenzione del modello di applicazione che:

  • Applica un trasformatore di parametri a tutte le route di attributi in un'applicazione.
  • Personalizza i valori dei token delle route di attributi quando vengono sostituiti.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Il metodo precedente ListAll corrisponde a /subscription-management/list-all.

l'oggetto RouteTokenTransformerConvention viene registrato come opzione:

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

Per la definizione di Slug, vedere la documentazione Web MDN su Slug .

Avviso

Quando si usa System.Text.RegularExpressions per elaborare l'input non attendibile, passare un timeout. Un utente malintenzionato può fornire input per RegularExpressions causare un attacco Denial of Service. Le API del framework ASP.NET Core che usano RegularExpressions passano un timeout.

Più route di attributi

Il routing con attributi supporta la definizione di più route che raggiungono la stessa azione. L'uso più comune è simulare il comportamento della route convenzionale predefinita come illustrato nell'esempio seguente:

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

L'inserimento di più attributi di route nel controller significa che ognuno di essi si combina con ognuno degli attributi di route nei metodi di azione:

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

Tutti i vincoli di route del verbo HTTP implementano IActionConstraint.

Quando vengono posizionati più attributi di route che implementano IActionConstraint su un'azione:

  • Ogni vincolo di azione viene combinato con il modello di route applicato al controller.
[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();
    }
}

L'uso di più route sulle azioni potrebbe sembrare utile e potente, è preferibile mantenere lo spazio URL dell'app di base e ben definito. Usare più route per le azioni solo se necessario, ad esempio, per supportare i client esistenti.

Definizione di parametri facoltativi, valori predefiniti e vincoli della route con attributi

Le route con attributi supportano la stessa sintassi inline delle route convenzionali per specificare i parametri facoltativi, i valori predefiniti e i vincoli.

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

Nel codice [HttpPost("product14/{id:int}")] precedente applica un vincolo di route. L'azione Products14Controller.ShowProduct viene confrontata solo in base ai percorsi URL, ad esempio /product14/3. La parte {id:int} del modello di route vincola tale segmento solo a numeri interi.

Vedere Riferimento per i modelli di route per una descrizione dettagliata della sintassi del modello di route.

Attributi di route personalizzati con IRouteTemplateProvider

Tutti gli attributi della route implementano IRouteTemplateProvider. Runtime di ASP.NET Core:

  • Cerca gli attributi nelle classi controller e nei metodi di azione all'avvio dell'app.
  • Usa gli attributi che implementano IRouteTemplateProvider per compilare il set iniziale di route.

Implementare IRouteTemplateProvider per definire attributi di route personalizzati. Ogni IRouteTemplateProvider consente di definire una singola route con un modello di route, un ordinamento e un nome personalizzati:

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

Il metodo precedente Get restituisce Order = 2, Template = api/MyTestApi.

Usare il modello di applicazione per personalizzare le route degli attributi

Il modello di applicazione:

  • Modello a oggetti creato all'avvio in Program.cs.
  • Contiene tutti i metadati usati da ASP.NET Core per instradare ed eseguire le azioni in un'app.

Il modello di applicazione include tutti i dati raccolti dagli attributi di route. I dati degli attributi di route vengono forniti dall'implementazione IRouteTemplateProvider . Convenzioni:

  • Può essere scritto per modificare il modello di applicazione per personalizzare il comportamento del routing.
  • Vengono lette all'avvio dell'app.

Questa sezione illustra un esempio di base della personalizzazione del routing tramite il modello di applicazione. Il codice seguente rende le route approssimativamente allineate alla struttura di cartelle del progetto.

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

Il codice seguente impedisce l'applicazione della namespace convenzione ai controller indirizzati:

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

Ad esempio, il controller seguente non usa NamespaceRoutingConvention:

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

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

Il metodo NamespaceRoutingConvention.Apply:

  • Non esegue alcuna operazione se il controller è instradato.
  • Imposta il modello di controller in base a namespace, con la base namespace rimossa.

L'oggetto NamespaceRoutingConvention può essere applicato in 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();

Si consideri ad esempio il controller seguente:

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

Nel codice precedente:

  • La base namespace è My.Application.
  • Il nome completo del controller precedente è My.Application.Admin.Controllers.UsersController.
  • Imposta NamespaceRoutingConvention il modello controller su Admin/Controllers/Users/[action]/{id?.

Può NamespaceRoutingConvention anche essere applicato come attributo in un controller:

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

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

Routing misto: routing con attributi e routing convenzionale

ASP.NET le app core possono combinare l'uso del routing convenzionale e del routing degli attributi. È tipico usare route convenzionali per i controller che servono pagine HTML per i browser e il routing degli attributi per i controller che servono REST le API.

Le azioni vengono indirizzate in modo convenzionale o con attributi. Se una route viene inserita nel controller o nell'azione, viene indirizzata con attributi. Le azioni che definiscono le route con attributi non possono essere raggiunte usando le route convenzionali e viceversa. Qualsiasi attributo di route nel controller esegue tutte le azioni nell'attributo controller indirizzato.

Il routing degli attributi e il routing convenzionale usano lo stesso motore di routing.

Routing con caratteri speciali

Il routing con caratteri speciali può causare risultati imprevisti. Si consideri ad esempio un controller con il metodo di azione seguente:

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

Quando string id contiene i valori codificati seguenti, potrebbero verificarsi risultati imprevisti:

ASCII Encoded
/ %2F
+

I parametri di route non sono sempre decodificati in URL. Questo problema potrebbe essere risolto in futuro. Per altre informazioni, vedere questo problema di GitHub;

Generazione url e valori di ambiente

Le app possono usare le funzionalità di generazione url di routing per generare collegamenti URL alle azioni. La generazione di URL elimina gli URL hardcoded , rendendo il codice più affidabile e gestibile. Questa sezione è incentrata sulle funzionalità di generazione di URL fornite da MVC e illustrano solo le nozioni di base sul funzionamento della generazione di URL. Vedere Routing per una descrizione dettagliata della generazione di URL.

L'interfaccia IUrlHelper è l'elemento sottostante dell'infrastruttura tra MVC e routing per la generazione di URL. Un'istanza di IUrlHelper è disponibile tramite la Url proprietà nei controller, nelle visualizzazioni e nei componenti di visualizzazione.

Nell'esempio seguente l'interfaccia IUrlHelper viene usata tramite la Controller.Url proprietà per generare un URL a un'altra azione.

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

Se l'app usa la route convenzionale predefinita, il valore della url variabile è la stringa /UrlGeneration/Destinationdi percorso URL . Questo percorso URL viene creato tramite il routing combinando:

  • Valori di route della richiesta corrente, denominati valori di ambiente.
  • I valori passati a Url.Action e sostituendo tali valori nel modello di route:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Il valore di ogni parametro di route del modello di route viene sostituito attraverso la corrispondenza dei nomi con i valori e i valori di ambiente. Un parametro di route che non ha un valore può:

  • Usare un valore predefinito se ne ha uno.
  • Essere ignorato se è facoltativo. Ad esempio, dal id modello {controller}/{action}/{id?}di route .

La generazione dell'URL ha esito negativo se un parametro di route obbligatorio non ha un valore corrispondente. Se la generazione di URL non riesce per una route, viene tentata la route successiva finché non vengono tentate tutte le route o viene trovata una corrispondenza.

L'esempio precedente di Url.Action presuppone il routing convenzionale. La generazione di URL funziona in modo analogo con il routing degli attributi, anche se i concetti sono diversi. Con il routing convenzionale:

  • I valori di route vengono usati per espandere un modello.
  • I valori di route per controller e action in genere vengono visualizzati in tale modello. Ciò funziona perché gli URL corrispondenti al routing rispettano una convenzione.

L'esempio seguente usa il routing degli attributi:

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

L'azione Source nel codice precedente genera custom/url/to/destination.

LinkGenerator è stato aggiunto in ASP.NET Core 3.0 come alternativa a IUrlHelper. LinkGenerator offre funzionalità simili ma più flessibili. Ogni metodo in IUrlHelper ha anche una famiglia di metodi corrispondente su LinkGenerator .

Generazione di URL in base al nome dell'azione

Url.Action, LinkGenerator.GetPathByAction e tutti gli overload correlati sono progettati per generare l'endpoint di destinazione specificando un nome del controller e un nome di azione.

Quando si usa Url.Action, i valori di route correnti per controller e action vengono forniti dal runtime:

  • Il valore di controller e action fa parte sia dei valori di ambiente che dei valori. Il metodo Url.Action usa sempre i valori correnti di action e controller genera un percorso URL che instrada all'azione corrente.

Il routing tenta di usare i valori nei valori di ambiente per inserire informazioni non specificate durante la generazione di un URL. Si consideri una route come {a}/{b}/{c}/{d} con i valori { a = Alice, b = Bob, c = Carol, d = David }di ambiente :

  • Il routing contiene informazioni sufficienti per generare un URL senza valori aggiuntivi.
  • Il routing contiene informazioni sufficienti perché tutti i parametri di route hanno un valore.

Se il valore { d = Donovan } viene aggiunto:

  • Il valore { d = David } viene ignorato.
  • Il percorso URL generato è Alice/Bob/Carol/Donovan.

Avviso: i percorsi URL sono gerarchici. Nell'esempio precedente, se il valore { c = Cheryl } viene aggiunto:

  • Entrambi i valori { c = Carol, d = David } vengono ignorati.
  • Non esiste più un valore per d e la generazione di URL non riesce.
  • I valori desiderati di c e d devono essere specificati per generare un URL.

È possibile che si verifichi questo problema con la route {controller}/{action}/{id?}predefinita . Questo problema è raro in pratica perché Url.Action specifica sempre in modo esplicito un controller valore e action .

Diversi overload di Url.Action accettano un oggetto valori di route per fornire valori per i parametri di route diversi da controller e action. L'oggetto valori di route viene spesso usato con id. Ad esempio: Url.Action("Buy", "Products", new { id = 17 }). Oggetto valori di route:

  • Per convenzione è in genere un oggetto di tipo anonimo.
  • Può essere un oggetto IDictionary<> o un POCO.

I valori di route aggiuntivi che non corrispondono a parametri di route vengono inseriti nella stringa di query.

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

Il codice precedente genera /Products/Buy/17?color=red.

Il codice seguente genera un URL assoluto:

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

Per creare un URL assoluto, usare una delle opzioni seguenti:

  • Overload che accetta un oggetto protocol. Ad esempio, il codice precedente.
  • LinkGenerator.GetUriByAction, che genera gli URI assoluti per impostazione predefinita.

Generare URL per route

Il codice precedente ha illustrato la generazione di un URL passando il controller e il nome dell'azione. IUrlHelper fornisce anche la famiglia di metodi Url.RouteUrl . Questi metodi sono simili a Url.Action, ma non copiano i valori correnti di e controller nei valori di action route. Utilizzo più comune di Url.RouteUrl:

  • Specifica un nome di route per generare l'URL.
  • In genere non specifica un nome di controller o azione.
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();
    }

Il file seguente Razor genera un collegamento HTML a Destination_Route:

<h1>Test Links</h1>

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

Generare URL in HTML e Razor

IHtmlHelper fornisce i HtmlHelper metodi Html.BeginForm e Html.ActionLink per generare <form> e <a> gli elementi rispettivamente. Questi metodi usano il metodo Url.Action per generare un URL e accettano argomenti simili. Gli oggetti Url.RouteUrl complementi di HtmlHelper sono Html.BeginRouteForm e Html.RouteLink e hanno una funzionalità simile.

Gli helper tag generano gli URL attraverso l'helper tag form e l'helper tag <a>. Entrambi usano IUrlHelper per la propria implementazione. Per altre informazioni, vedere Tag Helpers in forms (Helper tag nei moduli ).

All'interno delle visualizzazioni IUrlHelper è reso disponibile dalla proprietà Url per tutte le generazioni di URL ad hoc che non rientrano nelle situazioni descritte in precedenza.

Generazione di URL nei risultati dell'azione

Negli esempi precedenti è stato illustrato l'uso IUrlHelper di in un controller. L'utilizzo più comune in un controller consiste nel generare un URL come parte di un risultato di un'azione.

Le classi di base ControllerBase e Controller offrono metodi pratici per i risultati delle azioni che fanno riferimento a un'altra azione. Un utilizzo tipico consiste nel reindirizzare dopo aver accettato l'input dell'utente:

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

I metodi factory dei risultati dell'azione, RedirectToAction ad esempio e CreatedAtAction seguono un modello simile ai metodi in IUrlHelper.

Caso speciale per le route convenzionali dedicate

Il routing convenzionale può usare un tipo speciale di definizione di route denominato route convenzionale dedicato. Nell'esempio seguente la route denominata blog è una route convenzionale dedicata:

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

Usando le definizioni di route precedenti, Url.Action("Index", "Home") genera il percorso / URL usando la default route, ma perché? Si potrebbe pensare che i valori di route { controller = Home, action = Index } siano sufficienti per generare un URL usando blog e che il risultato sia /blog?action=Index&controller=Home.

Le route convenzionali dedicate si basano su un comportamento speciale di valori predefiniti che non hanno un parametro di route corrispondente che impedisce che la route sia troppo greedy con la generazione di URL. In questo caso i valori predefiniti sono { controller = Blog, action = Article } e né controlleraction vengono visualizzati come parametri di route. Quando il routing esegue la generazione di URL, i valori specificati devono corrispondere ai valori predefiniti. La generazione di URL con blog ha esito negativo perché i valori { controller = Home, action = Index } non corrispondono a { controller = Blog, action = Article }. Il routing quindi esegue il fallback per provare default, che ha esito positivo.

Aree

Le aree sono una funzionalità MVC usata per organizzare le funzionalità correlate in un gruppo come separato:

  • Spazio dei nomi di routing per le azioni del controller.
  • Struttura di cartelle per le visualizzazioni.

L'uso delle aree consente a un'app di avere più controller con lo stesso nome, purché abbiano aree diverse. Usando le aree si crea una gerarchia per il routing aggiungendo un altro parametro di route, area a controller e action. In questa sezione viene illustrato come il routing interagisce con le aree. Per informazioni dettagliate sull'uso delle aree con le visualizzazioni, vedere Aree .

L'esempio seguente configura MVC per l'uso della route convenzionale predefinita e di una area route per un area denominato 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();

Nel codice precedente viene MapAreaControllerRoute chiamato per creare l'oggetto "blog_route". Il secondo parametro, "Blog", è il nome dell'area.

Quando si corrisponde a un percorso URL come /Manage/Users/AddUser, la "blog_route" route genera i valori { area = Blog, controller = Users, action = AddUser }di route . Il valore della area route viene prodotto da un valore predefinito per area. La route creata da MapAreaControllerRoute è equivalente alla seguente:

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

MapAreaControllerRoute crea una route che usa sia un valore predefinito che un vincolo per area usando il nome di area specificato, in questo caso Blog. Il valore predefinito assicura che la route generi sempre { area = Blog, ... }, il vincolo richiede il valore { area = Blog, ... } per la generazione di URL.

Il routing convenzionale dipende dall'ordinamento. In generale, le rotte con aree devono essere posizionate in precedenza perché sono più specifiche di quelle senza un'area.

Usando l'esempio precedente, i valori { area = Blog, controller = Users, action = AddUser } della route corrispondono all'azione seguente:

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

L'attributo [Area] indica un controller come parte di un'area. Questo controller si trova nell'area Blog . I controller senza un [Area] attributo non sono membri di alcuna area e non corrispondono quando il valore della area route viene fornito dal routing. Nell'esempio seguente solo il primo controller indicato può corrispondere ai valori di route { 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}");
        }
    }
}

Lo spazio dei nomi di ogni controller viene visualizzato qui per completezza. Se i controller precedenti usavano lo stesso spazio dei nomi, verrà generato un errore del compilatore. Gli spazi dei nomi delle classi non hanno effetto sul routing di MVC.

I primi due controller sono membri di aree e corrispondono solo quando viene specificato il relativo nome di area dal valore di route area. Il terzo controller non è un membro di un'area e può corrispondere solo quando non vengono specificati valori per area dal routing.

In termini di corrispondenza con nessun valore, l'assenza del valore area è come se il valore per area fosse Null o la stringa vuota.

Quando si esegue un'azione all'interno di un'area, il valore di route per area è disponibile come valore di ambiente per il routing da usare per la generazione di URL. Ciò significa che per impostazione predefinita le aree funzionano temporaneamente per la generazione di URL, come illustrato nell'esempio seguente.

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

Il codice seguente genera un URL per /Zebra/Users/AddUser:

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

Definizione dell'azione

I metodi pubblici in un controller, ad eccezione di quelli con l'attributo NonAction , sono azioni.

Codice di esempio

Eseguire il debug della diagnostica

Per un output di diagnostica di routing dettagliato, impostare su Logging:LogLevel:MicrosoftDebug. Nell'ambiente di sviluppo impostare il livello di log in appsettings.Development.json:

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

ASP.NET Controller di base usano il middleware di routing per trovare le corrispondenze con gli URL delle richieste in ingresso ed eseguirne il mapping alle azioni. Modelli di route:

  • Vengono definiti nel codice di avvio o negli attributi.
  • Descrivere in che modo i percorsi URL vengono confrontati con le azioni.
  • Vengono usati per generare URL per i collegamenti. I collegamenti generati vengono in genere restituiti nelle risposte.

Le azioni sono instradate convenzionalmente o instradate con attributi. L'inserimento di una route sul controller o sull'azione rende l'attributo indirizzato. Per altre informazioni, vedere Routing misto.

Questo documento:

  • Illustra le interazioni tra MVC e routing:
    • Come le app MVC tipiche usano le funzionalità di routing.
    • Copre entrambi:
    • Vedere Routing per informazioni dettagliate sul routing avanzato.
  • Fa riferimento al sistema di routing predefinito aggiunto in ASP.NET Core 3.0, denominato routing degli endpoint. È possibile usare i controller con la versione precedente del routing a scopo di compatibilità. Per istruzioni, vedere la guida alla migrazione 2.2-3.0. Per informazioni di riferimento sul sistema di routing legacy, vedere la versione 2.2 di questo documento .

Configurare la route convenzionale

Startup.Configure in genere ha codice simile al seguente quando si usa il routing convenzionale:

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

All'interno della chiamata a UseEndpointsviene MapControllerRoute usato per creare una singola route. La singola route è denominata default route. La maggior parte delle app con controller e visualizzazioni usa un modello di route simile alla default route. REST Le API devono usare il routing degli attributi.

Modello di route "{controller=Home}/{action=Index}/{id?}":

  • Corrisponde a un percorso URL, ad esempio /Products/Details/5

  • Estrae i valori { controller = Products, action = Details, id = 5 } di route tramite tokenizzazione del percorso. L'estrazione dei valori di route genera una corrispondenza se l'app ha un controller denominato ProductsController e un'azione Details :

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

    MyDisplayRouteInfo viene fornito dal pacchetto NuGet Rick.Docs.Samples.RouteInfo e visualizza le informazioni sulla route.

  • /Products/Details/5 model associa il valore di id = 5 per impostare il id parametro su 5. Per altri dettagli, vedere Associazione di modelli.

  • {controller=Home} definisce Home come valore predefinito controller.

  • {action=Index} definisce Index come valore predefinito action.

  • Il ? carattere in {id?} definisce id come facoltativo.

  • I parametri di route predefiniti e facoltativi non devono necessariamente essere presenti nel percorso URL per trovare una corrispondenza. Vedere Riferimento per i modelli di route per una descrizione dettagliata della sintassi del modello di route.

  • Trova la corrispondenza con il percorso /URL .

  • Produce i valori { controller = Home, action = Index }di route .

I valori per controller e action usano i valori predefiniti. id non produce un valore perché non esiste alcun segmento corrispondente nel percorso URL. / corrisponde solo se esiste un'azione HomeController e Index :

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

Usando la definizione del controller e il modello di route precedenti, l'azione HomeController.Index viene eseguita per i percorsi URL seguenti:

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

Il percorso / URL usa i controller e Index l'azione predefiniti Home del modello di route. Il percorso /Home URL usa l'azione predefinita Index del modello di route.

Il metodo pratico MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Sostituisce:

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

Importante

Il routing viene configurato usando il UseRoutingmiddleware , MapControllerRoutee MapAreaControllerRoute . Per usare i controller:

Routing convenzionale

Il routing convenzionale viene usato con controller e visualizzazioni. La route predefinita (default):

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

Il precedente è un esempio di route convenzionale. Si chiama routing convenzionale perché stabilisce una convenzione per i percorsi URL:

  • Il primo segmento di percorso, {controller=Home}, esegue il mapping al nome del controller.
  • Il secondo segmento, {action=Index}, esegue il mapping al nome dell'azione.
  • Il terzo segmento viene {id?} usato per un oggetto facoltativo id. l'oggetto ? in {id?} lo rende facoltativo. id viene usato per eseguire il mapping a un'entità del modello.

Usando questa default route, il percorso URL:

  • /Products/List esegue il mapping all'azione ProductsController.List .
  • /Blog/Article/17 esegue il mapping a BlogController.Article e in genere il modello associa il id parametro a 17.

Questo mapping:

  • Si basa solo sui nomi di controller e azione.
  • Non si basa su spazi dei nomi, percorsi dei file di origine o parametri del metodo.

L'uso del routing convenzionale con la route predefinita consente di creare l'app senza dover creare un nuovo modello di URL per ogni azione. Per un'app con azioni di stile CRUD , avere coerenza per gli URL tra i controller:

  • Semplifica il codice.
  • Rende l'interfaccia utente più prevedibile.

Avviso

Nel id codice precedente viene definito come facoltativo dal modello di route. Le azioni possono essere eseguite senza l'ID facoltativo fornito come parte dell'URL. In genere, quando id viene omesso dall'URL:

  • id è impostato su per associazione di 0 modelli.
  • Nessuna entità viene trovata nel database corrispondente a id == 0.

Il routing degli attributi fornisce un controllo granulare per rendere l'ID necessario per alcune azioni e non per altri. Per convenzione, la documentazione include parametri facoltativi come id quando è probabile che vengano visualizzati nell'utilizzo corretto.

La maggior parte delle app dovrebbe scegliere uno schema di routing semplice e descrittivo in modo che gli URL siano leggibili e significativi. La route convenzionale predefinita {controller=Home}/{action=Index}/{id?}:

  • Supporta uno schema di routing semplice e descrittivo.
  • È un punto iniziale utile per le app basate su interfaccia utente.
  • È l'unico modello di route necessario per molte app dell'interfaccia utente Web. Per le app dell'interfaccia utente Web di grandi dimensioni, un'altra route che usa Aree è spesso tutto ciò che serve.

MapControllerRoute e MapAreaRoute :

  • Assegnare automaticamente un valore di ordine agli endpoint in base all'ordine in cui vengono richiamati.

Routing degli endpoint in ASP.NET Core 3.0 e versioni successive:

  • Non ha un concetto di route.
  • Non fornisce garanzie di ordinamento per l'esecuzione dell'estendibilità, tutti gli endpoint vengono elaborati contemporaneamente.

Abilitare la registrazione per verificare in che modo le implementazioni del routing predefinite, ad esempio Route, corrispondono alle richieste.

Il routing degli attributi è illustrato più avanti in questo documento.

Più route convenzionali

È possibile aggiungere più route convenzionali all'interno UseEndpoints aggiungendo altre chiamate a MapControllerRoute e MapAreaControllerRoute. In questo modo è possibile definire più convenzioni o aggiungere route convenzionali dedicate a un'azione specifica, ad esempio:

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

La blog route nel codice precedente è una route convenzionale dedicata. Si chiama route convenzionale dedicata perché:

Poiché controller e action non vengono visualizzati nel modello "blog/{*article}" di route come parametri:

  • Possono avere solo i valori { controller = "Blog", action = "Article" }predefiniti.
  • Questa route esegue sempre il mapping all'azione BlogController.Article.

/Blog, /Blog/Articlee /Blog/{any-string} sono gli unici percorsi URL che corrispondono alla route del blog.

L'esempio precedente:

  • blog la route ha una priorità più alta per le corrispondenze rispetto alla default route perché viene aggiunta per prima.
  • Esempio di routing dello stile Slug in cui è tipico avere un nome di articolo come parte dell'URL.

Avviso

In ASP.NET Core 3.0 e versioni successive, il routing non è:

  • Definire un concetto denominato route. UseRouting aggiunge la corrispondenza della route alla pipeline middleware. Il UseRouting middleware esamina il set di endpoint definiti nell'app e seleziona la corrispondenza dell'endpoint migliore in base alla richiesta.
  • Fornire garanzie sull'ordine di esecuzione dell'estendibilità, ad esempio IRouteConstraint o IActionConstraint.

Vedere Routing per materiale di riferimento sul routing.

Ordine di routing convenzionale

Il routing convenzionale corrisponde solo a una combinazione di azioni e controller definiti dall'app. Questo è progettato per semplificare i casi in cui le route convenzionali si sovrappongono. L'aggiunta di route tramite MapControllerRoute, MapDefaultControllerRoutee MapAreaControllerRoute assegna automaticamente un valore di ordine agli endpoint in base all'ordine in cui vengono richiamate. Le corrispondenze di una route visualizzata in precedenza hanno una priorità più alta. Il routing convenzionale dipende dall'ordinamento. In generale, le rotte con aree devono essere posizionate in precedenza perché sono più specifiche di quelle senza un'area. Le route convenzionali dedicate con parametri di route catch-all come {*article} possono creare una route troppo greedy, ovvero corrisponde agli URL che si intende associare ad altre route. Inserire le route greedy più avanti nella tabella di route per impedire corrispondenze greedy.

Avviso

Un parametro catch-all può corrispondere erroneamente alle route a causa di un bug nel routing. Le app interessate da questo bug presentano le caratteristiche seguenti:

  • Un itinerario catch-all, ad esempio {**slug}"
  • La route catch-all non riesce a trovare una corrispondenza con le richieste che deve corrispondere.
  • La rimozione di altre route rende la route catch-all iniziare a funzionare.

Vedere Bug di GitHub 18677 e 16579 per casi di esempio che hanno raggiunto questo bug.

Una correzione di consenso esplicito per questo bug è contenuta in .NET Core 3.1.301 SDK e versioni successive. Il codice seguente imposta un commutatore interno che corregge questo bug:

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

Risoluzione di azioni ambigue

Quando due endpoint corrispondono tramite routing, il routing deve eseguire una delle operazioni seguenti:

  • Scegliere il candidato migliore.
  • Generazione di un'eccezione.

Ad esempio:

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

Il controller precedente definisce due azioni che corrispondono:

  • Percorso URL /Products33/Edit/17
  • Indirizzare i dati { controller = Products33, action = Edit, id = 17 }.

Si tratta di un modello tipico per i controller MVC:

  • Edit(int) visualizza un modulo per modificare un prodotto.
  • Edit(int, Product) elabora il modulo pubblicato.

Per risolvere la route corretta:

  • Edit(int, Product) viene selezionato quando la richiesta è un http POST.
  • Edit(int) viene selezionato quando il verbo HTTP è qualsiasi altro elemento. Edit(int) viene in genere chiamato tramite GET.

L'oggetto HttpPostAttribute, [HttpPost]viene fornito al routing in modo che possa scegliere in base al metodo HTTP della richiesta. rende HttpPostAttributeEdit(int, Product) una corrispondenza migliore rispetto a Edit(int).

È importante comprendere il ruolo degli attributi, ad esempio HttpPostAttribute. Gli attributi simili sono definiti per altri verbi HTTP. Nel routing convenzionale, è comune che le azioni usino lo stesso nome di azione quando fanno parte di un modulo di presentazione, inviare il flusso di lavoro del modulo. Ad esempio, vedere Esaminare i due metodi di azione Edit.

Se il routing non è in grado di scegliere un candidato migliore, viene generata un'eccezione AmbiguousMatchException , elencando più endpoint corrispondenti.

Nomi di route convenzionali

Le stringhe "blog" e "default" negli esempi seguenti sono nomi di route convenzionali:

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

I nomi delle route assegnano alla route un nome logico. La route denominata può essere usata per la generazione di URL. L'uso di una route denominata semplifica la creazione di URL quando l'ordinamento delle route potrebbe rendere complessa la generazione di URL. I nomi di route devono essere univoci a livello di applicazione.

Nomi di route:

  • Non avere alcun impatto sulla corrispondenza dell'URL o sulla gestione delle richieste.
  • Vengono usati solo per la generazione di URL.

Il concetto di nome della route è rappresentato nel routing come IEndpointNameMetadata. I termini nome della route e nome dell'endpoint:

  • Sono intercambiabili.
  • Quello usato nella documentazione e nel codice dipende dall'API descritta.

Routing degli attributi per REST le API

REST Le API devono usare il routing degli attributi per modellare la funzionalità dell'app come set di risorse in cui le operazioni sono rappresentate da verbi HTTP.

Il routing con attributi usa un set di attributi per eseguire il mapping delle azioni direttamente ai modelli di route. Il codice seguente StartUp.Configure è tipico per un'API REST e viene usato nell'esempio seguente:

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

Nel codice precedente viene MapControllers chiamato all'interno UseEndpoints per eseguire il mapping dei controller indirizzati con attributi.

Nell'esempio seguente :

  • HomeController corrisponde a un set di URL simili a quello che corrisponde alla route {controller=Home}/{action=Index}/{id?} convenzionale predefinita.
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);
    }
}

L'azione HomeController.Index viene eseguita per uno dei percorsi /URL , , /Home/Index/Homeo /Home/Index/3.

In questo esempio viene evidenziata una differenza di programmazione chiave tra il routing degli attributi e il routing convenzionale. Il routing degli attributi richiede più input per specificare una route. La route predefinita convenzionale gestisce le route in modo più conciso. Tuttavia, il routing degli attributi consente e richiede un controllo preciso dei modelli di route applicabili a ogni azione.

Con il routing degli attributi, i nomi dei controller e delle azioni non fanno parte della corrispondenza dell'azione, a meno che non venga usata la sostituzione del token. L'esempio seguente corrisponde agli stessi URL dell'esempio precedente:

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

Il codice seguente usa la sostituzione dei token per action e 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();
    }
}

Il codice seguente si applica [Route("[controller]/[action]")] al controller:

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

Nel codice precedente, i Index modelli di metodo devono anteporre / o ~/ ai modelli di route. I modelli di route applicati a un'azione che iniziano con / o ~/ non vengono combinati con i modelli di route applicati al controller.

Per informazioni sulla selezione del modello di route, vedere Precedenza del modello di route.

Nomi riservati di routing

Le parole chiave seguenti sono nomi di parametri di route riservate quando si usano controller o Razor pagine:

  • action
  • area
  • controller
  • handler
  • page

L'uso page di come parametro di route con il routing degli attributi è un errore comune. In questo modo si verifica un comportamento incoerente e confuso con la generazione di URL.

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

I nomi dei parametri speciali vengono usati dalla generazione di URL per determinare se un'operazione di generazione url fa riferimento a una Razor pagina o a un controller.

Le parole chiave seguenti sono riservate nel contesto di una Razor visualizzazione o di una Razor pagina:

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

Queste parole chiave non devono essere usate per le generazioni di collegamenti, i parametri associati al modello o le proprietà di livello superiore.

Modelli di verbo HTTP

ASP.NET Core include i modelli di verbo HTTP seguenti:

Modelli di route

ASP.NET Core include i modelli di route seguenti:

  • Tutti i modelli di verbo HTTP sono modelli di route.
  • [Route]

Routing degli attributi con attributi verbi HTTP

Prendere in considerazione il controller seguente:

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

Nel codice precedente:

  • Ogni azione contiene l'attributo [HttpGet] , che vincola solo la corrispondenza alle richieste HTTP GET.
  • L'azione GetProduct include il "{id}" modello, quindi id viene aggiunto al modello nel "api/[controller]" controller. Il modello di metodi è "api/[controller]/{id}". Pertanto, questa azione corrisponde solo alle richieste GET per il modulo /api/test2/xyz,/api/test2/123/api/test2/{any string} e così via.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • L'azione GetIntProduct contiene il "int/{id:int}" modello. La :int parte del modello vincola i valori di id route alle stringhe che possono essere convertite in un numero intero. Richiesta GET a /api/test2/int/abc:
    • Non corrisponde a questa azione.
    • Restituisce un errore 404 Non trovato .
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • L'azione GetInt2Product contiene {id} nel modello, ma non limita id i valori che possono essere convertiti in un numero intero. Richiesta GET a /api/test2/int2/abc:
    • Corrisponde a questa route.
    • L'associazione di modelli non riesce a eseguire la conversione abc in un numero intero. Il id parametro del metodo è integer.
    • Restituisce una richiesta non valida 400 perché l'associazione di modelli non è riuscita a eseguire la conversione abc in un numero intero.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Il routing degli attributi può usare HttpMethodAttribute attributi come HttpPostAttribute, HttpPutAttributee HttpDeleteAttribute. Tutti gli attributi del verbo HTTP accettano un modello di route. L'esempio seguente mostra due azioni che corrispondono allo stesso modello di route:

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

Uso del percorso /products3URL :

  • L'azione MyProductsController.ListProducts viene eseguita quando il verbo HTTP è GET.
  • L'azione MyProductsController.CreateProduct viene eseguita quando il verbo HTTP è POST.

Quando si compila un'API REST , è raro che sia necessario usare [Route(...)] in un metodo di azione perché l'azione accetta tutti i metodi HTTP. È preferibile usare l'attributo verbo HTTP più specifico per essere preciso su ciò che supporta l'API. I client delle REST API devono conoscere i percorsi e i verbi HTTP mappati a operazioni logiche specifiche.

REST Le API devono usare il routing degli attributi per modellare la funzionalità dell'app come set di risorse in cui le operazioni sono rappresentate da verbi HTTP. Ciò significa che molte operazioni, ad esempio GET e POST nella stessa risorsa logica, usano lo stesso URL. Il routing degli attributi offre un livello di controllo necessario per progettare con attenzione il layout dell'endpoint pubblico di un'API.

Poiché una route con attributi si applica a un'azione specifica, è facile fare in modo che i parametri siano richiesti come parte della definizione del modello di route. Nell'esempio id seguente è necessario come parte del percorso URL:

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

Azione Products2ApiController.GetProduct(int) :

  • Viene eseguito con il percorso URL, ad esempio /products2/3
  • Non viene eseguito con il percorso /products2URL .

L'attributo [Consumes] consente a un'azione di limitare i tipi di contenuto della richiesta supportati. Per altre informazioni, vedere Definire i tipi di contenuto di richiesta supportati con l'attributo Consumes.

Vedere Routing per una descrizione completa dei modelli di route e delle opzioni correlate.

Per altre informazioni su [ApiController], vedere Attributo ApiController.

Nome route

Il codice seguente definisce un nome di route di Products_List:

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

I nomi di route possono essere usati per generare un URL in base a un percorso specifico. Nomi di route:

  • Non avere alcun impatto sul comportamento di corrispondenza dell'URL del routing.
  • Vengono usati solo per la generazione di URL.

I nomi delle route devono essere univoci a livello di applicazione.

Confrontare il codice precedente con la route predefinita convenzionale, che definisce il id parametro come facoltativo ({id?}). La possibilità di specificare con precisione le API presenta vantaggi, ad esempio consentire /products e /products/5 inviare a diverse azioni.

Combinazione di route degli attributi

Per rendere il routing con attributi meno ripetitivo, gli attributi di route del controller vengono combinati con gli attributi di route delle singole azioni. I modelli di route definiti per il controller vengono anteposti ai modelli di route delle azioni. Inserendo un attributo di route nel controller, tutte le azioni presenti nel controller useranno il routing con attributi.

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

Nell'esempio precedente:

  • Il percorso /products URL può corrispondere ProductsApi.ListProducts
  • Il percorso /products/5 URL può corrispondere a ProductsApi.GetProduct(int).

Entrambe queste azioni corrispondono solo a HTTP GET perché sono contrassegnate con l'attributo [HttpGet] .

I modelli di route applicati a un'azione che iniziano con / o ~/ non vengono combinati con i modelli di route applicati al controller. L'esempio seguente corrisponde a un set di percorsi URL simili alla route predefinita.

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

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

La tabella seguente illustra gli [Route] attributi nel codice precedente:

Attributo Combina con [Route("Home")] Definisce il modello di route
[Route("")] "Home"
[Route("Index")] "Home/Index"
[Route("/")] No ""
[Route("About")] "Home/About"

Ordine di route degli attributi

Il routing compila un albero e corrisponde a tutti gli endpoint contemporaneamente:

  • Le voci di route si comportano come se fossero inserite in un ordinamento ideale.
  • Le route più specifiche possono essere eseguite prima delle route più generali.

Ad esempio, una route di attributi come blog/search/{topic} è più specifica di una route di attributi come blog/{*article}. La blog/search/{topic} route ha priorità più alta, per impostazione predefinita, perché è più specifica. Usando il routing convenzionale, lo sviluppatore è responsabile dell'inserimento di route nell'ordine desiderato.

Le route degli attributi possono configurare un ordine usando la Order proprietà . Tutti gli attributi di route forniti dal framework includono Order . Le route vengono elaborate in base a un ordinamento crescente della proprietà Order. L'ordine predefinito è 0. Impostazione di una route tramite Order = -1 esecuzioni prima delle route che non impostano un ordine. Impostazione di una route tramite l'esecuzione Order = 1 dopo l'ordinamento predefinito della route.

Evitare di dipendere da Order. Se lo spazio URL di un'app richiede valori di ordine espliciti per il routing corretto, è probabile che si confonda anche con i client. In generale, il routing degli attributi seleziona la route corretta con la corrispondenza dell'URL. Se l'ordine predefinito usato per la generazione di URL non funziona, l'uso di un nome di route come override è in genere più semplice rispetto all'applicazione della Order proprietà .

Si considerino i due controller seguenti che definiscono entrambi la route corrispondente /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);
    }
}

La richiesta /home con il codice precedente genera un'eccezione simile alla seguente:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

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

L'aggiunta Order a uno degli attributi di route risolve l'ambiguità:

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

Con il codice precedente, /home esegue l'endpoint HomeController.Index . Per accedere a MyDemoController.MyIndex, richiedere /home/MyIndex. Nota:

  • Il codice precedente è un esempio o una progettazione di routing scadente. È stato usato per illustrare la Order proprietà .
  • La Order proprietà risolve solo l'ambiguità, che il modello non può corrispondere. Sarebbe preferibile rimuovere il [Route("Home")] modello.

Per informazioni sull'ordine di route con Pagine con Razor Pagine, vedi Razor Convenzioni di route e app.

In alcuni casi, viene restituito un errore HTTP 500 con route ambigue. Usare la registrazione per vedere quali endpoint hanno causato l'oggetto AmbiguousMatchException.

Sostituzione dei token nei modelli di route [controller], [azione], [area]

Per praticità, le route degli attributi supportano la sostituzione dei token racchiudendo un token tra parentesi quadre ([, ]). I token [action], [area]e [controller] vengono sostituiti con i valori del nome dell'azione, del nome dell'area e del nome del controller dall'azione in cui è definita la route:

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

Nel codice precedente:

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

La sostituzione dei token avviene come ultimo passaggio della creazione delle route con attributi. L'esempio precedente si comporta come il codice seguente:

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

Se lo si legge in una lingua diversa dall'inglese, segnalarlo in questo problema di discussione su GitHub se si vogliono visualizzare i commenti del codice nella lingua nativa.

Le route con attributi possono anche essere combinate con l'ereditarietà. Questo è potente combinato con la sostituzione dei token. La sostituzione dei token si applica anche ai nomi di route definiti dalle route con attributi. [Route("[controller]/[action]", Name="[controller]_[action]")]genera un nome di route univoco per ogni azione:

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

Per verificare la corrispondenza del delimitatore letterale della sostituzione di token [ o ], eseguirne l'escape ripetendo il carattere ([[ o ]]).

Usare un trasformatore di parametri per personalizzare la sostituzione dei token

La sostituzione dei token può essere personalizzata usando un trasformatore di parametri. Un trasformatore di parametri implementa IOutboundParameterTransformer e trasforma il valore dei parametri. Ad esempio, un trasformatore di parametro personalizzato SlugifyParameterTransformer modifica il valore della SubscriptionManagement route in 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 è una convenzione del modello di applicazione che:

  • Applica un trasformatore di parametri a tutte le route di attributi in un'applicazione.
  • Personalizza i valori dei token delle route di attributi quando vengono sostituiti.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Il metodo precedente ListAll corrisponde a /subscription-management/list-all.

La convenzione RouteTokenTransformerConvention è registrata come un'opzione in ConfigureServices.

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

Per la definizione di Slug, vedere la documentazione Web MDN su Slug .

Avviso

Quando si usa System.Text.RegularExpressions per elaborare l'input non attendibile, passare un timeout. Un utente malintenzionato può fornire input per RegularExpressions causare un attacco Denial of Service. Le API del framework ASP.NET Core che usano RegularExpressions passano un timeout.

Più route di attributi

Il routing con attributi supporta la definizione di più route che raggiungono la stessa azione. L'uso più comune è simulare il comportamento della route convenzionale predefinita come illustrato nell'esempio seguente:

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

L'inserimento di più attributi di route nel controller significa che ognuno di essi si combina con ognuno degli attributi di route nei metodi di azione:

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

Tutti i vincoli di route del verbo HTTP implementano IActionConstraint.

Quando vengono posizionati più attributi di route che implementano IActionConstraint su un'azione:

  • Ogni vincolo di azione viene combinato con il modello di route applicato al controller.
[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();
    }
}

L'uso di più route sulle azioni potrebbe sembrare utile e potente, è preferibile mantenere lo spazio URL dell'app di base e ben definito. Usare più route per le azioni solo se necessario, ad esempio, per supportare i client esistenti.

Definizione di parametri facoltativi, valori predefiniti e vincoli della route con attributi

Le route con attributi supportano la stessa sintassi inline delle route convenzionali per specificare i parametri facoltativi, i valori predefiniti e i vincoli.

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

Nel codice [HttpPost("product14/{id:int}")] precedente applica un vincolo di route. L'azione Products14Controller.ShowProduct viene confrontata solo in base ai percorsi URL, ad esempio /product14/3. La parte {id:int} del modello di route vincola tale segmento solo a numeri interi.

Vedere Riferimento per i modelli di route per una descrizione dettagliata della sintassi del modello di route.

Attributi di route personalizzati con IRouteTemplateProvider

Tutti gli attributi della route implementano IRouteTemplateProvider. Runtime di ASP.NET Core:

  • Cerca gli attributi nelle classi controller e nei metodi di azione all'avvio dell'app.
  • Usa gli attributi che implementano IRouteTemplateProvider per compilare il set iniziale di route.

Implementare IRouteTemplateProvider per definire attributi di route personalizzati. Ogni IRouteTemplateProvider consente di definire una singola route con un modello di route, un ordinamento e un nome personalizzati:

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

Il metodo precedente Get restituisce Order = 2, Template = api/MyTestApi.

Usare il modello di applicazione per personalizzare le route degli attributi

Il modello di applicazione:

  • Modello a oggetti creato all'avvio.
  • Contiene tutti i metadati usati da ASP.NET Core per instradare ed eseguire le azioni in un'app.

Il modello di applicazione include tutti i dati raccolti dagli attributi di route. I dati degli attributi di route vengono forniti dall'implementazione IRouteTemplateProvider . Convenzioni:

  • Può essere scritto per modificare il modello di applicazione per personalizzare il comportamento del routing.
  • Vengono lette all'avvio dell'app.

Questa sezione illustra un esempio di base della personalizzazione del routing tramite il modello di applicazione. Il codice seguente rende le route approssimativamente allineate alla struttura di cartelle del progetto.

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

Il codice seguente impedisce l'applicazione della namespace convenzione ai controller indirizzati:

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

Ad esempio, il controller seguente non usa NamespaceRoutingConvention:

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

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

Il metodo NamespaceRoutingConvention.Apply:

  • Non esegue alcuna operazione se il controller è instradato.
  • Imposta il modello di controller in base a namespace, con la base namespace rimossa.

L'oggetto NamespaceRoutingConvention può essere applicato in 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.

Si consideri ad esempio il controller seguente:

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

Nel codice precedente:

  • La base namespace è My.Application.
  • Il nome completo del controller precedente è My.Application.Admin.Controllers.UsersController.
  • Imposta NamespaceRoutingConvention il modello controller su Admin/Controllers/Users/[action]/{id?.

Può NamespaceRoutingConvention anche essere applicato come attributo in un controller:

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

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

Routing misto: routing con attributi e routing convenzionale

ASP.NET le app core possono combinare l'uso del routing convenzionale e del routing degli attributi. È tipico usare route convenzionali per i controller che servono pagine HTML per i browser e il routing degli attributi per i controller che servono REST le API.

Le azioni vengono indirizzate in modo convenzionale o con attributi. Se una route viene inserita nel controller o nell'azione, viene indirizzata con attributi. Le azioni che definiscono le route con attributi non possono essere raggiunte usando le route convenzionali e viceversa. Qualsiasi attributo di route nel controller esegue tutte le azioni nell'attributo controller indirizzato.

Il routing degli attributi e il routing convenzionale usano lo stesso motore di routing.

Generazione url e valori di ambiente

Le app possono usare le funzionalità di generazione url di routing per generare collegamenti URL alle azioni. La generazione di URL elimina gli URL hardcoding, rendendo il codice più affidabile e gestibile. Questa sezione è incentrata sulle funzionalità di generazione di URL fornite da MVC e illustrano solo le nozioni di base sul funzionamento della generazione di URL. Vedere Routing per una descrizione dettagliata della generazione di URL.

L'interfaccia IUrlHelper è l'elemento sottostante dell'infrastruttura tra MVC e routing per la generazione di URL. Un'istanza di IUrlHelper è disponibile tramite la Url proprietà nei controller, nelle visualizzazioni e nei componenti di visualizzazione.

Nell'esempio seguente l'interfaccia IUrlHelper viene usata tramite la Controller.Url proprietà per generare un URL a un'altra azione.

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

Se l'app usa la route convenzionale predefinita, il valore della url variabile è la stringa /UrlGeneration/Destinationdi percorso URL . Questo percorso URL viene creato tramite il routing combinando:

  • Valori di route della richiesta corrente, denominati valori di ambiente.
  • I valori passati a Url.Action e sostituendo tali valori nel modello di route:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Il valore di ogni parametro di route del modello di route viene sostituito attraverso la corrispondenza dei nomi con i valori e i valori di ambiente. Un parametro di route che non ha un valore può:

  • Usare un valore predefinito se ne ha uno.
  • Essere ignorato se è facoltativo. Ad esempio, dal id modello {controller}/{action}/{id?}di route .

La generazione dell'URL ha esito negativo se un parametro di route obbligatorio non ha un valore corrispondente. Se la generazione di URL non riesce per una route, viene tentata la route successiva finché non vengono tentate tutte le route o viene trovata una corrispondenza.

L'esempio precedente di Url.Action presuppone il routing convenzionale. La generazione di URL funziona in modo analogo con il routing degli attributi, anche se i concetti sono diversi. Con il routing convenzionale:

  • I valori di route vengono usati per espandere un modello.
  • I valori di route per controller e action in genere vengono visualizzati in tale modello. Ciò funziona perché gli URL corrispondenti al routing rispettano una convenzione.

L'esempio seguente usa il routing degli attributi:

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

L'azione Source nel codice precedente genera custom/url/to/destination.

LinkGenerator è stato aggiunto in ASP.NET Core 3.0 come alternativa a IUrlHelper. LinkGenerator offre funzionalità simili ma più flessibili. Ogni metodo in IUrlHelper ha anche una famiglia di metodi corrispondente su LinkGenerator .

Generazione di URL in base al nome dell'azione

Url.Action, LinkGenerator.GetPathByAction e tutti gli overload correlati sono progettati per generare l'endpoint di destinazione specificando un nome del controller e un nome di azione.

Quando si usa Url.Action, i valori di route correnti per controller e action vengono forniti dal runtime:

  • Il valore di controller e action fa parte sia dei valori di ambiente che dei valori. Il metodo Url.Action usa sempre i valori correnti di action e controller genera un percorso URL che instrada all'azione corrente.

Il routing tenta di usare i valori nei valori di ambiente per inserire informazioni non specificate durante la generazione di un URL. Si consideri una route come {a}/{b}/{c}/{d} con i valori { a = Alice, b = Bob, c = Carol, d = David }di ambiente :

  • Il routing contiene informazioni sufficienti per generare un URL senza valori aggiuntivi.
  • Il routing contiene informazioni sufficienti perché tutti i parametri di route hanno un valore.

Se il valore { d = Donovan } viene aggiunto:

  • Il valore { d = David } viene ignorato.
  • Il percorso URL generato è Alice/Bob/Carol/Donovan.

Avviso: i percorsi URL sono gerarchici. Nell'esempio precedente, se il valore { c = Cheryl } viene aggiunto:

  • Entrambi i valori { c = Carol, d = David } vengono ignorati.
  • Non esiste più un valore per d e la generazione di URL non riesce.
  • I valori desiderati di c e d devono essere specificati per generare un URL.

È possibile che si verifichi questo problema con la route {controller}/{action}/{id?}predefinita . Questo problema è raro in pratica perché Url.Action specifica sempre in modo esplicito un controller valore e action .

Diversi overload di Url.Action accettano un oggetto valori di route per fornire valori per i parametri di route diversi da controller e action. L'oggetto valori di route viene spesso usato con id. Ad esempio: Url.Action("Buy", "Products", new { id = 17 }). Oggetto valori di route:

  • Per convenzione è in genere un oggetto di tipo anonimo.
  • Può essere un oggetto IDictionary<> o un POCO.

I valori di route aggiuntivi che non corrispondono a parametri di route vengono inseriti nella stringa di query.

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

Il codice precedente genera /Products/Buy/17?color=red.

Il codice seguente genera un URL assoluto:

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

Per creare un URL assoluto, usare una delle opzioni seguenti:

  • Overload che accetta un oggetto protocol. Ad esempio, il codice precedente.
  • LinkGenerator.GetUriByAction, che genera gli URI assoluti per impostazione predefinita.

Generare URL per route

Il codice precedente ha illustrato la generazione di un URL passando il controller e il nome dell'azione. IUrlHelper fornisce anche la famiglia di metodi Url.RouteUrl . Questi metodi sono simili a Url.Action, ma non copiano i valori correnti di e controller nei valori di action route. Utilizzo più comune di Url.RouteUrl:

  • Specifica un nome di route per generare l'URL.
  • In genere non specifica un nome di controller o azione.
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();
    }

Il file seguente Razor genera un collegamento HTML a Destination_Route:

<h1>Test Links</h1>

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

Generare URL in HTML e Razor

IHtmlHelper fornisce i HtmlHelper metodi Html.BeginForm e Html.ActionLink per generare <form> e <a> gli elementi rispettivamente. Questi metodi usano il metodo Url.Action per generare un URL e accettano argomenti simili. Gli oggetti Url.RouteUrl complementi di HtmlHelper sono Html.BeginRouteForm e Html.RouteLink e hanno una funzionalità simile.

Gli helper tag generano gli URL attraverso l'helper tag form e l'helper tag <a>. Entrambi usano IUrlHelper per la propria implementazione. Per altre informazioni, vedere Tag Helpers in forms (Helper tag nei moduli ).

All'interno delle visualizzazioni IUrlHelper è reso disponibile dalla proprietà Url per tutte le generazioni di URL ad hoc che non rientrano nelle situazioni descritte in precedenza.

Generazione di URL nei risultati dell'azione

Negli esempi precedenti è stato illustrato l'uso IUrlHelper di in un controller. L'utilizzo più comune in un controller consiste nel generare un URL come parte di un risultato di un'azione.

Le classi di base ControllerBase e Controller offrono metodi pratici per i risultati delle azioni che fanno riferimento a un'altra azione. Un utilizzo tipico consiste nel reindirizzare dopo aver accettato l'input dell'utente:

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

I metodi factory dei risultati dell'azione, RedirectToAction ad esempio e CreatedAtAction seguono un modello simile ai metodi in IUrlHelper.

Caso speciale per le route convenzionali dedicate

Il routing convenzionale può usare un tipo speciale di definizione di route denominato route convenzionale dedicato. Nell'esempio seguente la route denominata blog è una route convenzionale dedicata:

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

Usando le definizioni di route precedenti, Url.Action("Index", "Home") genera il percorso / URL usando la default route, ma perché? Si potrebbe pensare che i valori di route { controller = Home, action = Index } siano sufficienti per generare un URL usando blog e che il risultato sia /blog?action=Index&controller=Home.

Le route convenzionali dedicate si basano su un comportamento speciale di valori predefiniti che non hanno un parametro di route corrispondente che impedisce che la route sia troppo greedy con la generazione di URL. In questo caso i valori predefiniti sono { controller = Blog, action = Article } e né controlleraction vengono visualizzati come parametri di route. Quando il routing esegue la generazione di URL, i valori specificati devono corrispondere ai valori predefiniti. La generazione di URL con blog ha esito negativo perché i valori { controller = Home, action = Index } non corrispondono a { controller = Blog, action = Article }. Il routing quindi esegue il fallback per provare default, che ha esito positivo.

Aree

Le aree sono una funzionalità MVC usata per organizzare le funzionalità correlate in un gruppo come separato:

  • Spazio dei nomi di routing per le azioni del controller.
  • Struttura di cartelle per le visualizzazioni.

L'uso delle aree consente a un'app di avere più controller con lo stesso nome, purché abbiano aree diverse. Usando le aree si crea una gerarchia per il routing aggiungendo un altro parametro di route, area a controller e action. In questa sezione viene illustrato come il routing interagisce con le aree. Per informazioni dettagliate sull'uso delle aree con le visualizzazioni, vedere Aree .

L'esempio seguente configura MVC per l'uso della route convenzionale predefinita e di una area route per un area denominato Blog:

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

Nel codice precedente viene MapAreaControllerRoute chiamato per creare l'oggetto "blog_route". Il secondo parametro, "Blog", è il nome dell'area.

Quando si corrisponde a un percorso URL come /Manage/Users/AddUser, la "blog_route" route genera i valori { area = Blog, controller = Users, action = AddUser }di route . Il valore della area route viene prodotto da un valore predefinito per area. La route creata da MapAreaControllerRoute è equivalente alla seguente:

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 crea una route che usa sia un valore predefinito che un vincolo per area usando il nome di area specificato, in questo caso Blog. Il valore predefinito assicura che la route generi sempre { area = Blog, ... }, il vincolo richiede il valore { area = Blog, ... } per la generazione di URL.

Il routing convenzionale dipende dall'ordinamento. In generale, le rotte con aree devono essere posizionate in precedenza perché sono più specifiche di quelle senza un'area.

Usando l'esempio precedente, i valori { area = Blog, controller = Users, action = AddUser } della route corrispondono all'azione seguente:

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

L'attributo [Area] indica un controller come parte di un'area. Questo controller si trova nell'area Blog . I controller senza un [Area] attributo non sono membri di alcuna area e non corrispondono quando il valore della area route viene fornito dal routing. Nell'esempio seguente solo il primo controller indicato può corrispondere ai valori di route { 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}");
        }
    }
}

Lo spazio dei nomi di ogni controller viene visualizzato qui per completezza. Se i controller precedenti usavano lo stesso spazio dei nomi, verrà generato un errore del compilatore. Gli spazi dei nomi delle classi non hanno effetto sul routing di MVC.

I primi due controller sono membri di aree e corrispondono solo quando viene specificato il relativo nome di area dal valore di route area. Il terzo controller non è un membro di un'area e può corrispondere solo quando non vengono specificati valori per area dal routing.

In termini di corrispondenza con nessun valore, l'assenza del valore area è come se il valore per area fosse Null o la stringa vuota.

Quando si esegue un'azione all'interno di un'area, il valore di route per area è disponibile come valore di ambiente per il routing da usare per la generazione di URL. Ciò significa che per impostazione predefinita le aree funzionano temporaneamente per la generazione di URL, come illustrato nell'esempio seguente.

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

Il codice seguente genera un URL per /Zebra/Users/AddUser:

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

Definizione dell'azione

I metodi pubblici in un controller, ad eccezione di quelli con l'attributo NonAction , sono azioni.

Codice di esempio

Eseguire il debug della diagnostica

Per un output di diagnostica di routing dettagliato, impostare su Logging:LogLevel:MicrosoftDebug. Nell'ambiente di sviluppo impostare il livello di log in appsettings.Development.json:

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