Routing delle azioni del controller in ASP.NET Core

Di Ryan Nowak, Kirk Larkin e Rick Anderson

Note

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

Warning

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere .NET e .NET Core Support Policy. Per la versione corrente, vedere la versione .NET 10 di questo articolo.

ASP.NET Core controller usano il routing middleware per trovare le corrispondenze con gli URL delle richieste in ingresso ed eseguirne il mapping a actions. Modelli di route:

  • Vengono definiti all'avvio in Program.cs 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. Inserendo una route sul controller o sull'azione, diventa indirizzata tramite attributi. 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 come 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.

Impostare il percorso convenzionale

Il modello MVC ASP.NET Core genera un codice di routing convenzionale simile all'esempio 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();

Usare MapControllerRoute per creare una singola route. L'unico percorso è il default percorso. 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 } della route suddividendo il percorso in token. 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 collega il valore di id = 5 per impostare il parametro id su 5. Per altre informazioni, 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. Per una descrizione dettagliata della sintassi del modello di route, vedere Riferimento al modello di route.
  • Trova la corrispondenza con il percorso URL /.

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

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 il controller e l'azione Home del modello di route predefinito Index. Il percorso /Home URL usa l'azione predefinita Index del modello di route.

Il metodo pratico MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Replaces:

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

Important

Il routing viene configurato mediante i middleware UseRouting 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 usando UseRouting e UseEndpoints. Per altre informazioni, vedere Routing in ASP.NET Core.

Routing convenzionale

Usare il routing convenzionale con controller e visualizzazioni. Il default percorso è:

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

Il codice 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}, corrisponde al nome del controller.
  • Il secondo segmento, {action=Index}, corrisponde al nome dell'azione.
  • Il terzo segmento {id?} viene usato per un id facoltativo. l'oggetto ? in {id?} lo rende facoltativo. id corrisponde a un'entità del modello.

Usando questa default route, il percorso URL:

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

Questa mappatura

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

Usando il routing convenzionale con la route predefinita, non è necessario 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.

Warning

Il modello di percorso definisce id come facoltativo. Le azioni possono essere eseguite senza l'ID facoltativo fornito come parte dell'URL. In genere, quando id viene omesso dall'URL:

  • Il binding del modello imposta id su 0.
  • Nessuna entità viene trovata nel database corrispondente a id == 0.

Il routing basato su attributi fornisce un controllo granulare per rendere l'ID necessario per alcune azioni e non per altre. 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:

  • Non ha il concetto di percorsi.
  • Non fornisce garanzie sull'ordinamento per l'esecuzione dell'estendibilità del sistema. 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. Quando si aggiungono queste chiamate, è 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 è un percorso convenzionale dedicato. Si tratta di un itinerario convenzionale dedicato perché:

Poiché il modello "blog/{*article}" di route non include controller e action 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.

Nell'esempio precedente:

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

Warning

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'applicazione e seleziona la migliore corrispondenza per l'endpoint in base alla richiesta.
  • Fornire garanzie sull'ordine di esecuzione delle estensioni, come 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 approccio semplifica i casi in cui le route convenzionali si sovrappongono. Quando si aggiungono route usando MapControllerRoute, MapDefaultControllerRoutee MapAreaControllerRoute, gli endpoint ottengono automaticamente un valore dell'ordine in base all'ordine in cui si richiamano questi metodi. Le corrispondenze di una route visualizzata in precedenza nell'elenco hanno una priorità più alta. Il routing convenzionale dipende dall'ordine. In generale, posizionare prima i percorsi con aree perché sono più specifici rispetto alle rotte senza aree. Le route convenzionali dedicate con parametri catch-all della route, come {*article}, possono rendere una route troppo generica. Una route greedy corrisponde agli URL che devono essere confrontati con altre route. Inserire le rotte avide più avanti nella tabella delle rotte per impedire corrispondenze avide.

Warning

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 percorso onnicomprensivo, ad esempio {**slug}"
  • La route catch-all non riesce a gestire correttamente le richieste che dovrebbe soddisfare.
  • La rimozione di altre route fa funzionare il percorso catch-all.

Consultare GitHub bug 18677 e 16579 per esempi di casi che riscontrano questo bug.

Una correzione di consenso esplicito per questo bug è contenuta in .NET Core 3.1.301 o versione successiva dell'SDK. 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.
  • Genera un'eccezione.

Per 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. 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 tradizionale, le azioni usano spesso lo stesso nome quando fanno parte di un flusso di lavoro di visualizzazione e invio del modulo. Ad esempio, vedere Esaminare i due metodi di azione di modifica.

Se il routing non è in grado di scegliere il candidato migliore, genera un'eccezione AmbiguousMatchException ed elenca i 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. Il percorso denominato può essere usato 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 ha alcun impatto sulla corrispondenza degli 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 le REST 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 precedente si usa MapControllers per mappare i controller con routing basato su 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 qualsiasi dei percorsi URL /, /Home, /Home/Index o /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ù dati 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 influenzano la corrispondenza dell'azione, a meno che non sia utilizzata la sostituzione di 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 per il routing

Le parole chiave seguenti sono nomi riservati dei parametri di route quando si utilizzano Controller o pagine Razor:

  • action
  • area
  • controller
  • handler
  • page

L'uso di page come parametro di route nel routing degli attributi è un errore comune. Questa scelta comporta comportamenti incoerenti e confusi con la generazione di URL.

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

La generazione di URL usa questi nomi di parametri speciali 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

Non usare queste parole chiave per le generazioni di collegamenti, i parametri associati al modello o le proprietà di primo livello.

Modelli di verbo HTTP

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

Modelli di route

ASP.NET Core includono i seguenti modelli di route:

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

Routing degli attributi con attributi dei 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 modello "{id}", quindi id viene aggiunto al modello "api/[controller]" sul controller. Il modello del metodo è "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. Una 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. Una 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. 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. Questa progettazione significa che molte operazioni, ad esempio GET e POST nella stessa risorsa logica, usano lo stesso URL. Il routing degli attributi fornisce il livello di controllo necessario per progettare attentamente 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 URL /products2.

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 maggiori informazioni su [ApiController], consultare l'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);
    }
}

Usare i nomi di route per generare un URL basato su una route specifica. Nomi di route:

  • Non influenzare il modo in cui il routing verifica la corrispondenza degli URL.
  • 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 di inviare /products e /products/5 a diverse azioni.

Combinare i percorsi attributo

Per rendere il routing degli attributi meno ripetitivo, combinare gli attributi di routing sul controller con gli attributi di routing sulle singole azioni. I modelli di route definiti nel controller vengono anteposti ai modelli di route delle azioni. Quando si inserisce un attributo di route nel controller, tutte le azioni nel controller usano il routing degli 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 URL /products 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] .

Modelli di route applicati a un'azione e 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();
    }
}

Nella tabella seguente vengono illustrati gli [Route] attributi nel codice precedente.

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

Ordine di route degli attributi

Il routing crea un albero e confronta tutti gli endpoint contemporaneamente.

  • Le voci di percorso si comportano come se fossero posizionate in un ordine ideale.
  • Le rotte più specifiche hanno una possibilità di essere eseguite prima delle rotte 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 rotte vengono elaborate in base a un ordinamento crescente della proprietà Order. L'ordine predefinito è 0. Impostare una rotta tramite Order = -1 viene eseguita prima delle rotte che non impostano un ordine. Impostare una route utilizzando Order = 1 viene eseguito dopo l'ordinamento predefinito delle route.

Evitare di fare affidamento suOrder. Se lo spazio URL di un'app richiede valori di ordine espliciti per instradare correttamente, è probabile che sia fonte di confusione anche per i clienti. In generale, l'instradamento basato sugli attributi seleziona il percorso corretto con l'allineamento 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 tramite 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 di progettazione di routing non ottimale. Illustra la proprietà Order.
  • La Order proprietà risolve solo l'ambiguità. Tale modello non può essere confrontato. È preferibile rimuovere il [Route("Home")] modello.

Per informazioni sull'ordine dei percorsi con Razor Pages, consulta Razor Convenzioni di routing e dell'app Pages: Ordine dei percorsi.

In alcuni casi, viene restituito un errore HTTP 500 con delle route ambigue. Utilizzare i log per vedere quali endpoint hanno causato il 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 si definisce 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 viene eseguita come ultimo passaggio della compilazione delle route dell'attributo. 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 stai leggendo questo argomento in una lingua diversa dall'inglese, comunicaci in questo GitHub problema di discussione se vuoi visualizzare i commenti del codice nella tua lingua nativa.

È anche possibile combinare route di attributi con ereditarietà. Questa combinazione è efficace quando si usa 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

È possibile personalizzare la sostituzione dei token usando un trasformatore di parametro. 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:

  • Applicare un trasformatore di parametro a tutte le route degli attributi in un'applicazione.
  • Personalizza i valori del token di route dell'attributo man mano che 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 dello slug, vedi la documentazione web di MDN sullo slug.

Warning

Quando si usa System.Text.RegularExpressions per elaborare l'input non attendibile, passare un timeout. Un utente malintenzionato può fornire un input a RegularExpressions, causando un attacco di negazione del servizio. ASP.NET Core API del framework che usano RegularExpressions passano un timeout.

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

Per una descrizione dettagliata della sintassi del modello di route, vedere Riferimento al modello di route.

Attributi di route personalizzati con IRouteTemplateProvider

Tutti gli attributi della route implementano IRouteTemplateProvider. Il 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 . Conventions:

  • 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 usando 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 che sono instradati per attributo.

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

Metodo NamespaceRoutingConvention.Apply:

  • Non esegue alcuna operazione se il controller è instradato con attributi.
  • 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 il modello del controller di NamespaceRoutingConvention 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

Le app di ASP.NET Core possono combinare insieme l'uso del routing di tipo convenzionale e del routing degli attributi. In genere, si utilizzano route convenzionali per i controller che forniscono pagine HTML ai browser e il routing basato su attributi per i controller che forniscono REST API.

Le azioni vengono indirizzate in modo convenzionale o mediante l'uso di attributi. Quando si definisce una route sul controller o sull'azione, essa diventa instradata tramite attributi. Non è possibile raggiungere le azioni che definiscono le route degli attributi attraverso 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 di 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 l'hardcoding degli URL, rendendo il codice più robusto e manutenibile. 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 . Il routing crea questo percorso URL combinando:

  • Valori di route della richiesta corrente, denominati valori di ambiente.
  • I valori passati a Url.Action e i valori sostituiti 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

In ogni parametro del modello di route, il valore viene sostituito facendo corrispondere i nomi con i valori specificati 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 di percorso {controller}/{action}/{id?}.

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, il runtime fornisce i valori di route correnti per controller e action:

  • I valori di controller e action fanno 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 un percorso come {a}/{b}/{c}/{d} con i valori di ambiente { a = Alice, b = Bob, c = Carol, d = David }.

  • 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 ha esito negativo.
  • È necessario specificare i valori desiderati di c e d 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 di valori di route per fornire valori per i parametri di route diversi da controller e action. L'oggetto di valori route viene spesso usato con id. Ad esempio: Url.Action("Buy", "Products", new { id = 17 }). L'oggetto dei valori di percorso

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

Tutti i valori di route aggiuntivi che non corrispondono ai 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:

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

Generare URL per percorso

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 action e controller nei valori del percorso. 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, vedi tag helper nei moduli.

All'interno delle visualizzazioni, l'oggetto IUrlHelper è disponibile tramite la Url proprietà per qualsiasi generazione di URL ad hoc non coperta dai metodi precedenti.

Generazione di URL nei risultati dell'azione

Negli esempi precedenti viene illustrato come usare IUrlHelper 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 delle azioni, come RedirectToAction e CreatedAtAction, seguono un modello simile a quello dei metodi su IUrlHelper.

Caso speciale per le route convenzionali dedicate

Il routing convenzionale può utilizzare un tipo speciale di definizione di percorso chiamato percorso 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é? È possibile che i valori { controller = Home, action = Index } di route siano sufficienti per generare un URL usando bloge il risultato sarebbe /blog?action=Index&controller=Home.

Percorsi convenzionali dedicati si basano su un comportamento speciale di valori predefiniti che non hanno un parametro di percorso corrispondente, il che impedisce che il percorso sia troppo eccessivo nella 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 passa al tentativo di utilizzare default, che ha esito positivo.

Areas

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

  • Namespace di routing per le azioni del controllo.
  • 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 dettagli sull'uso delle aree con le visualizzazioni, consultare 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, MapAreaControllerRoute viene chiamato per creare "blog_route". Il secondo parametro, "Blog", è il nome dell'area.

Quando si confronta un percorso URL come /Manage/Users/AddUser, la route "blog_route" genera i valori di route { area = Blog, controller = Users, action = AddUser }. Il valore della area route proviene da un valore predefinito per area. La route creata da MapAreaControllerRoute è equivalente al codice 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 usando sia un valore predefinito che un vincolo per area usando il nome dell'area specificato, in questo caso Blog. Il valore predefinito garantisce che il percorso produca sempre { area = Blog, ... } e che il vincolo richieda il valore { area = Blog, ... } per la generazione di URL.

Il routing convenzionale dipende dall'ordine. In generale, posizionare le rotte con aree in anticipo, poiché sono più specifiche delle rotte 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}");
        }
    }
}

Per completezza, lo spazio dei nomi di ogni controller viene visualizzato qui. 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 si abbinano solo quando il rispettivo nome dell'area è fornito 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 in modo persistente 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

Diagnostica del debug

Per ottenere un output diagnostico di routing dettagliato, impostare Logging:LogLevel:Microsoft su Debug. Nell'ambiente Development impostare il livello di registro in appsettings.Development.json:

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

ASP.NET Core controller usano il routing middleware per trovare le corrispondenze con gli URL delle richieste in ingresso ed eseguirne il mapping a actions. 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. Inserendo una route sul controller o sull'azione, diventa indirizzata tramite attributi. 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 .

Impostare il percorso convenzionale

Startup.Configure in genere ha codice simile all'esempio 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 } della route suddividendo il percorso in token. 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 collega il valore di id = 5 per impostare il parametro id su 5. Per altre informazioni, 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. Per una descrizione dettagliata della sintassi del modello di route, vedere Riferimento al modello di route.

  • Trova la corrispondenza con il percorso URL /.

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

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 il controller e l'azione Home del modello di route predefinito Index. Il percorso /Home URL usa l'azione predefinita Index del modello di route.

Il metodo pratico MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Replaces:

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

Important

L'instradamento viene configurato utilizzando i middleware UseRouting, MapControllerRoute e MapAreaControllerRoute. Per usare i controller:

Routing convenzionale

Usare il routing convenzionale con controller e visualizzazioni. Il default percorso è:

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

Il codice 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}, corrisponde al nome del controller.
  • Il secondo segmento, {action=Index}, corrisponde al nome dell'azione.
  • Il terzo segmento {id?} viene usato per un id facoltativo. l'oggetto ? in {id?} lo rende facoltativo. id corrisponde a un'entità del modello.

Usando questa default route, il percorso URL:

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

Questa mappatura

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

Usando il routing convenzionale con la route predefinita, non è necessario 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.

Warning

Il modello di percorso definisce id come facoltativo. Le azioni possono essere eseguite senza l'ID facoltativo fornito come parte dell'URL. In genere, quando id viene omesso dall'URL:

  • Il binding del modello imposta id su 0.
  • Nessuna entità viene trovata nel database corrispondente a id == 0.

Il routing basato su attributi fornisce un controllo granulare per rendere l'ID necessario per alcune azioni e non per altre. 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 o successivi:

  • Non ha il concetto di percorsi.
  • Non fornisce garanzie sull'ordinamento per l'esecuzione dell'estendibilità del sistema. 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 è un percorso convenzionale dedicato. Si tratta di un itinerario convenzionale dedicato perché:

Poiché il modello "blog/{*article}" di route non include controller e action 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.

Nell'esempio precedente:

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

Warning

In ASP.NET Core 3.0 o versione successiva, il routing non esegue:

  • Definire un concetto denominato route. UseRouting aggiunge la corrispondenza della route alla pipeline middleware. Il UseRouting middleware esamina il set di endpoint definiti nell'applicazione e seleziona la migliore corrispondenza per l'endpoint in base alla richiesta.
  • Fornire garanzie sull'ordine di esecuzione delle estensioni, come 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 approccio semplifica i casi in cui le route convenzionali si sovrappongono. Quando si aggiungono route usando MapControllerRoute, MapDefaultControllerRoutee MapAreaControllerRoute, gli endpoint ottengono automaticamente un valore dell'ordine in base all'ordine in cui si richiamano questi metodi. Le corrispondenze di una route visualizzata in precedenza nell'elenco hanno una priorità più alta. Il routing convenzionale dipende dall'ordine. In generale, posizionare prima i percorsi con aree perché sono più specifici rispetto alle rotte senza aree. Le route convenzionali dedicate con parametri catch-all della route, come {*article}, possono rendere una route troppo generica. Una route greedy corrisponde agli URL che devono essere confrontati con altre route. Inserire le rotte avide più avanti nella tabella delle rotte per impedire corrispondenze avide.

Warning

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 percorso onnicomprensivo, ad esempio {**slug}"
  • La route catch-all non riesce a gestire correttamente le richieste che dovrebbe soddisfare.
  • La rimozione di altre route fa funzionare il percorso catch-all.

Consultare GitHub bug 18677 e 16579 per esempi di casi che riscontrano questo bug.

Una correzione di consenso esplicito per questo bug è contenuta in .NET Core 3.1.301 o versione successiva dell'SDK. 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.
  • Genera un'eccezione.

Per 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. 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 dell'azione quando fanno parte di un flusso di lavoro di visualizzazione e invio del modulo. Ad esempio, vedere Esaminare i due metodi di azione di modifica.

Se il routing non è in grado di scegliere il candidato migliore, genera un'eccezione AmbiguousMatchException ed elenca i 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. Il percorso denominato può essere usato 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 ha alcun impatto sulla corrispondenza degli 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 le REST 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, MapControllers viene chiamato all'interno di UseEndpoints per eseguire il mapping dei controller con routing degli 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 qualsiasi dei percorsi URL /, /Home, /Home/Index o /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ù dati 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 influenzano la corrispondenza dell'azione, a meno che non sia utilizzata la sostituzione di 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 per il routing

Le parole chiave seguenti sono nomi riservati dei parametri di route quando si utilizzano Controller o pagine Razor:

  • action
  • area
  • controller
  • handler
  • page

L'uso di page come parametro di route nel routing degli attributi è un errore comune. Questa scelta comporta comportamenti incoerenti e confusi con la generazione di URL.

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

La generazione di URL usa questi nomi di parametri speciali 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

Non usare queste parole chiave per le generazioni di collegamenti, i parametri associati al modello o le proprietà di primo livello.

Modelli di verbo HTTP

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

Modelli di route

ASP.NET Core includono i seguenti modelli di route:

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

Routing degli attributi con attributi dei 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 modello "{id}", quindi id viene aggiunto al modello "api/[controller]" sul controller. Il modello del metodo è "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. Una 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. Una 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. Questa progettazione significa che molte operazioni, ad esempio GET e POST nella stessa risorsa logica, usano lo stesso URL. Il routing degli attributi fornisce il livello di controllo necessario per progettare attentamente 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 URL /products2.

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 maggiori informazioni su [ApiController], consultare l'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);
    }
}

Usare i nomi di route per generare un URL basato su una route specifica. Nomi di route:

  • Non influenzare il modo in cui il routing verifica la corrispondenza degli URL.
  • 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 di inviare /products e /products/5 a diverse azioni.

Combinare i percorsi attributo

Per rendere il routing degli attributi meno ripetitivo, combinare gli attributi di routing sul controller con gli attributi di routing sulle singole azioni. I modelli di route definiti nel controller vengono anteposti ai modelli di route delle azioni. Quando si inserisce un attributo di route nel controller, tutte le azioni nel controller usano il routing degli 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 URL /products 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] .

Modelli di route applicati a un'azione e 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();
    }
}

Nella tabella seguente vengono illustrati gli [Route] attributi nel codice precedente.

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

Ordine di route degli attributi

Il routing crea un albero e confronta tutti gli endpoint contemporaneamente.

  • Le voci di percorso si comportano come se fossero posizionate in un ordine ideale.
  • Le rotte più specifiche hanno una possibilità di essere eseguite prima delle rotte 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 rotte vengono elaborate in base a un ordinamento crescente della proprietà Order. L'ordine predefinito è 0. Impostare una rotta tramite Order = -1 viene eseguita prima delle rotte che non impostano un ordine. Impostare una route utilizzando Order = 1 viene eseguito dopo l'ordinamento predefinito delle route.

Evitare di fare affidamento suOrder. Se lo spazio URL di un'app richiede valori di ordine espliciti per instradare correttamente, è probabile che sia fonte di confusione anche per i clienti. In generale, l'instradamento basato sugli attributi seleziona il percorso corretto con l'allineamento 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 tramite 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 di progettazione di routing non ottimale. Illustra la proprietà Order.
  • La Order proprietà risolve solo l'ambiguità. Tale modello non può essere confrontato. È preferibile rimuovere il [Route("Home")] modello.

Per informazioni sull'ordine dei percorsi con Razor Pages, consulta Razor Convenzioni di routing e dell'app Pages: Ordine dei percorsi.

In alcuni casi, viene restituito un errore HTTP 500 con delle route ambigue. Utilizzare i log per vedere quali endpoint hanno causato il 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 si definisce 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 viene eseguita come ultimo passaggio della compilazione delle route dell'attributo. 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 stai leggendo questo argomento in una lingua diversa dall'inglese, comunicaci in questo GitHub problema di discussione se vuoi visualizzare i commenti del codice nella tua lingua nativa.

È anche possibile combinare route di attributi con ereditarietà. Questa combinazione è efficace quando si usa 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

È possibile personalizzare la sostituzione dei token usando un trasformatore di parametro. 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:

  • Applicare un trasformatore di parametro a tutte le route degli attributi in un'applicazione.
  • Personalizza i valori del token di route dell'attributo man mano che 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 RouteTokenTransformerConvention è registrata come opzione in ConfigureServices.

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

Per la definizione dello slug, vedi la documentazione web di MDN sullo slug.

Warning

Quando si usa System.Text.RegularExpressions per elaborare l'input non attendibile, passare un timeout. Un utente malintenzionato può fornire un input a RegularExpressions, causando un attacco di negazione del servizio. ASP.NET Core API del framework che usano RegularExpressions passano un timeout.

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

Per una descrizione dettagliata della sintassi del modello di route, vedere Riferimento al modello di route.

Attributi di route personalizzati con IRouteTemplateProvider

Tutti gli attributi della route implementano IRouteTemplateProvider. Il 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 . Conventions:

  • 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 usando 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 che sono instradati per attributo.

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

Metodo NamespaceRoutingConvention.Apply:

  • Non esegue alcuna operazione se il controller è instradato con attributi.
  • 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 il modello del controller di NamespaceRoutingConvention 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

Le app di ASP.NET Core possono combinare insieme l'uso del routing di tipo convenzionale e del routing degli attributi. In genere, si utilizzano route convenzionali per i controller che forniscono pagine HTML ai browser e il routing basato su attributi per i controller che forniscono REST API.

Le azioni vengono indirizzate in modo convenzionale o mediante l'uso di attributi. Quando una route viene aggiunta al controller o all'azione, viene configurata per l'instradamento basato su 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 di 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 . Il routing crea questo percorso URL combinando:

  • Valori di route della richiesta corrente, denominati valori di ambiente.
  • I valori passati a Url.Action e i valori sostituiti 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

In ogni parametro del modello di route, il valore viene sostituito facendo corrispondere i nomi con i valori specificati 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 di percorso {controller}/{action}/{id?}.

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, il runtime fornisce i valori di route correnti per controller e action:

  • I valori di controller e action fanno 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 un percorso come {a}/{b}/{c}/{d} con i valori di ambiente { a = Alice, b = Bob, c = Carol, d = David }.

  • 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 ha esito negativo.
  • È necessario specificare i valori desiderati di c e d 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 di valori di route per fornire valori per i parametri di route diversi da controller e action. L'oggetto di valori route viene spesso usato con id. Ad esempio: Url.Action("Buy", "Products", new { id = 17 }). L'oggetto dei valori di percorso

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

Tutti i valori di route aggiuntivi che non corrispondono ai 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:

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

Generare URL per percorso

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 action e controller nei valori del percorso. 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, vedi tag helper nei moduli.

All'interno delle visualizzazioni, l'oggetto IUrlHelper è disponibile tramite la Url proprietà per qualsiasi generazione di URL ad hoc non coperta dai metodi precedenti.

Generazione di URL nei risultati dell'azione

Negli esempi precedenti viene illustrato come usare IUrlHelper 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 delle azioni, come RedirectToAction e CreatedAtAction, seguono un modello simile a quello dei metodi su IUrlHelper.

Caso speciale per le route convenzionali dedicate

Il routing convenzionale può utilizzare un tipo speciale di definizione di percorso chiamato percorso 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é? È possibile che i valori { controller = Home, action = Index } di route siano sufficienti per generare un URL usando bloge il risultato sarebbe /blog?action=Index&controller=Home.

Percorsi convenzionali dedicati si basano su un comportamento speciale di valori predefiniti che non hanno un parametro di percorso corrispondente, il che impedisce che il percorso sia troppo eccessivo nella 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 passa al tentativo di utilizzare default, che ha esito positivo.

Areas

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

  • Namespace di routing per le azioni del controllo.
  • 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 dettagli sull'uso delle aree con le visualizzazioni, consultare 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, MapAreaControllerRoute viene chiamato per creare "blog_route". Il secondo parametro, "Blog", è il nome dell'area.

Quando si confronta un percorso URL come /Manage/Users/AddUser, la route "blog_route" genera i valori di route { area = Blog, controller = Users, action = AddUser }. Il valore della area route proviene da un valore predefinito per area. La route creata da MapAreaControllerRoute è equivalente al codice 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 usando sia un valore predefinito che un vincolo per area usando il nome dell'area specificato, in questo caso Blog. Il valore predefinito garantisce che il percorso produca sempre { area = Blog, ... } e che il vincolo richieda il valore { area = Blog, ... } per la generazione di URL.

Il routing convenzionale dipende dall'ordine. In generale, posizionare le rotte con aree in anticipo, poiché sono più specifiche delle rotte 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}");
        }
    }
}

Per completezza, lo spazio dei nomi di ogni controller viene visualizzato qui. 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 si abbinano solo quando il rispettivo nome dell'area è fornito 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 in modo persistente 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

Diagnostica del debug

Per ottenere un output diagnostico di routing dettagliato, impostare Logging:LogLevel:Microsoft su Debug. Nell'ambiente Development impostare il livello di registro in appsettings.Development.json:

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