Condividi tramite


Routing in ASP.NET Core

Di Ryan Nowak, Kirk Larkin e Rick Anderson

Nota

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

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

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

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

Il routing è responsabile della corrispondenza delle richieste HTTP in ingresso e dell'invio di tali richieste agli endpoint eseguibili dell'app. Gli endpoint sono le unità di misura dell'app del codice di gestione delle richieste eseguibili. Gli endpoint vengono definiti nell'app e configurati all'avvio dell'app. Il processo di corrispondenza dell'endpoint può estrarre i valori dall'URL della richiesta e fornire tali valori per l'elaborazione della richiesta. Usando le informazioni sull'endpoint dall'app, il routing è anche in grado di generare URL che eseguono il mapping agli endpoint.

Le app possono configurare il routing usando:

Questo articolo illustra i dettagli di basso livello del routing ASP.NET Core. Per informazioni sulla configurazione del routing:

Nozioni fondamentali sul routing

Il codice seguente illustra un esempio di routing di base:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

L'esempio precedente include un singolo endpoint usando il MapGet metodo :

  • Quando viene inviata una richiesta HTTP GET all'URL /radice :
    • Il delegato della richiesta viene eseguito.
    • Hello World! viene scritto nella risposta HTTP.
  • Se il metodo di richiesta non GET è o l'URL radice non /è , non viene restituita alcuna corrispondenza di route e viene restituito un HTTP 404.

Il routing usa una coppia di middleware, registrati da UseRouting e UseEndpoints:

  • UseRouting aggiunge la corrispondenza della route alla pipeline middleware. Questo middleware esamina il set di endpoint definiti nell'app e seleziona la corrispondenza migliore in base alla richiesta.
  • UseEndpoints aggiunge l'esecuzione dell'endpoint alla pipeline middleware. Esegue il delegato associato all'endpoint selezionato.

Le app in genere non devono chiamare UseRouting o UseEndpoints. WebApplicationBuilder configura una pipeline middleware che esegue il wrapping del middleware aggiunto in Program.cs con UseRouting e UseEndpoints. Tuttavia, le app possono modificare l'ordine in cui UseRouting ed eseguire UseEndpoints chiamando questi metodi in modo esplicito. Ad esempio, il codice seguente effettua una chiamata esplicita a UseRouting:

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

Nel codice precedente:

  • La chiamata a app.Use registra un middleware personalizzato eseguito all'inizio della pipeline.
  • La chiamata a UseRouting configura il middleware corrispondente alla route da eseguire dopo il middleware personalizzato.
  • L'endpoint registrato con MapGet viene eseguito alla fine della pipeline.

Se l'esempio precedente non includeva una chiamata a UseRouting, il middleware personalizzato verrebbe eseguito dopo il middleware corrispondente alla route.

Nota: le route aggiunte direttamente all'esecuzione WebApplication alla fine della pipeline.

Endpoint

Il MapGet metodo viene usato per definire un endpoint. Un endpoint è un elemento che può essere:

  • Selezionato, associando l'URL e il metodo HTTP.
  • Eseguito eseguendo il delegato .

Gli endpoint che possono essere confrontati ed eseguiti dall'app vengono configurati in UseEndpoints. Ad esempio, MapGet, MapPoste metodi simili connettono i delegati di richiesta al sistema di routing. È possibile usare metodi aggiuntivi per connettere ASP.NET funzionalità del framework Core al sistema di routing:

L'esempio seguente mostra il routing con un modello di route più sofisticato:

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

La stringa /hello/{name:alpha} è un modello di route. Viene usato un modello di route per configurare la corrispondenza dell'endpoint. In questo caso, il modello corrisponde a:

  • UN URL come /hello/Docs
  • Qualsiasi percorso URL che inizia con /hello/ seguito da una sequenza di caratteri alfabetici. :alpha applica un vincolo di route che corrisponde solo ai caratteri alfabetici. I vincoli di route vengono illustrati più avanti in questo articolo.

Secondo segmento del percorso URL, {name:alpha}:

L'esempio seguente mostra il routing con controlli di integrità e autorizzazione:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

L'esempio precedente illustra come:

  • Il middleware di autorizzazione può essere usato con il routing.
  • Gli endpoint possono essere usati per configurare il comportamento di autorizzazione.

La MapHealthChecks chiamata aggiunge un endpoint di controllo integrità. Il concatenamento RequireAuthorization a questa chiamata collega un criterio di autorizzazione all'endpoint.

La chiamata UseAuthentication e UseAuthorization aggiunge il middleware di autenticazione e autorizzazione. Questi middleware vengono posizionati tra UseRouting e UseEndpoints in modo che possano:

  • Vedere quale endpoint è stato selezionato da UseRouting.
  • Applicare un criterio di autorizzazione prima UseEndpoints dell'invio all'endpoint.

Metadati dell'endpoint

Nell'esempio precedente sono presenti due endpoint, ma solo l'endpoint di controllo integrità ha un criterio di autorizzazione associato. Se la richiesta corrisponde all'endpoint del controllo integrità, /healthzviene eseguito un controllo di autorizzazione. Ciò dimostra che gli endpoint possono avere dati aggiuntivi collegati. Questi dati aggiuntivi sono denominati metadati dell'endpoint:

  • I metadati possono essere elaborati tramite middleware compatibile con il routing.
  • I metadati possono essere di qualsiasi tipo .NET.

Concetti relativi al routing

Il sistema di routing si basa sulla pipeline middleware aggiungendo il concetto avanzato di endpoint . Gli endpoint rappresentano unità di funzionalità dell'app distinte l'una dall'altra in termini di routing, autorizzazione e qualsiasi numero di sistemi ASP.NET Core.

definizione dell'endpoint principale ASP.NET

Un endpoint ASP.NET Core è:

Il codice seguente illustra come recuperare ed esaminare l'endpoint corrispondente alla richiesta corrente:

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

L'endpoint, se selezionato, può essere recuperato da HttpContext. È possibile controllarne le proprietà. Gli oggetti endpoint non sono modificabili e non possono essere modificati dopo la creazione. Il tipo di endpoint più comune è .RouteEndpoint RouteEndpoint include informazioni che consentono di selezionarla dal sistema di routing.

Nel codice precedente, app. Usa configura un middleware inline.

Il codice seguente mostra che, a seconda della posizione in cui app.Use viene chiamato nella pipeline, potrebbe non esserci un endpoint:

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

Nell'esempio precedente vengono aggiunte Console.WriteLine istruzioni che indicano se è stato selezionato o meno un endpoint. Per maggiore chiarezza, l'esempio assegna un nome visualizzato all'endpoint fornito / .

L'esempio precedente include anche chiamate a UseRouting e UseEndpoints per controllare esattamente quando questi middleware vengono eseguiti all'interno della pipeline.

Esecuzione di questo codice con un URL di / visualizzazione:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

L'esecuzione di questo codice con qualsiasi altro URL viene visualizzata:

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Questo output dimostra che:

  • L'endpoint è sempre Null prima UseRouting di essere chiamato.
  • Se viene trovata una corrispondenza, l'endpoint non è Null tra UseRouting e UseEndpoints.
  • Il UseEndpoints middleware è terminale quando viene trovata una corrispondenza. Il middleware del terminale viene definito più avanti in questo articolo.
  • Middleware dopo UseEndpoints l'esecuzione solo quando non viene trovata alcuna corrispondenza.

Il UseRouting middleware usa il SetEndpoint metodo per collegare l'endpoint al contesto corrente. È possibile sostituire il middleware con la UseRouting logica personalizzata e ottenere comunque i vantaggi dell'uso degli endpoint. Gli endpoint sono primitivi di basso livello come middleware e non sono associati all'implementazione del routing. La maggior parte delle app non deve sostituire UseRouting con la logica personalizzata.

Il UseEndpoints middleware è progettato per essere usato in parallelo con il UseRouting middleware. La logica di base per eseguire un endpoint non è complicata. Usare GetEndpoint per recuperare l'endpoint e quindi richiamarne la RequestDelegate proprietà.

Il codice seguente illustra come il middleware può influenzare o reagire al routing:

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

L'esempio precedente illustra due concetti importanti:

  • Il middleware può essere eseguito prima UseRouting di modificare i dati su cui opera il routing.
  • Il middleware può essere eseguito tra UseRouting e UseEndpoints per elaborare i risultati del routing prima dell'esecuzione dell'endpoint.
    • Middleware eseguito tra UseRouting e UseEndpoints:
      • In genere controlla i metadati per comprendere gli endpoint.
      • Spesso prende decisioni di sicurezza, come fatto da UseAuthorization e UseCors.
    • La combinazione di middleware e metadati consente di configurare i criteri per endpoint.

Il codice precedente mostra un esempio di middleware personalizzato che supporta i criteri per endpoint. Il middleware scrive un log di controllo dell'accesso ai dati sensibili nella console. Il middleware può essere configurato per controllare un endpoint con i RequiresAuditAttribute metadati. Questo esempio illustra un modello di consenso esplicito in cui vengono controllati solo gli endpoint contrassegnati come sensibili. È possibile definire questa logica inversa, controllando tutto ciò che non è contrassegnato come sicuro, ad esempio. Il sistema di metadati dell'endpoint è flessibile. Questa logica può essere progettata in qualsiasi modo adatto al caso d'uso.

Il codice di esempio precedente è progettato per illustrare i concetti di base degli endpoint. L'esempio non è destinato all'uso in produzione. Una versione più completa di un middleware del log di controllo è la seguente:

  • Accedere a un file o a un database.
  • Includere dettagli, ad esempio l'utente, l'indirizzo IP, il nome dell'endpoint sensibile e altro ancora.

I metadati dei criteri RequiresAuditAttribute di controllo vengono definiti come un oggetto per un Attribute uso più semplice con framework basati su classi, ad esempio controller e SignalR. Quando si usa la route al codice:

  • I metadati vengono associati a un'API del generatore.
  • I framework basati su classi includono tutti gli attributi nel metodo e nella classe corrispondenti durante la creazione di endpoint.

Le procedure consigliate per i tipi di metadati sono di definirle come interfacce o attributi. Le interfacce e gli attributi consentono il riutilizzo del codice. Il sistema di metadati è flessibile e non impone alcuna limitazione.

Confrontare il middleware del terminale con il routing

L'esempio seguente illustra sia il middleware del terminale che il routing:

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Lo stile del middleware visualizzato con Approach 1: è il middleware del terminale. Si chiama middleware del terminale perché esegue un'operazione corrispondente:

  • L'operazione di corrispondenza nell'esempio precedente è Path == "/" relativa al middleware e Path == "/Routing" al routing.
  • Quando una corrispondenza ha esito positivo, esegue alcune funzionalità e restituisce, anziché richiamare il next middleware.

Si tratta di un middleware terminale perché termina la ricerca, esegue alcune funzionalità e quindi restituisce.

L'elenco seguente confronta il middleware del terminale con il routing:

  • Entrambi gli approcci consentono di terminare la pipeline di elaborazione:
    • Il middleware termina la pipeline restituendo anziché richiamando next.
    • Gli endpoint sono sempre terminale.
  • Il middleware del terminale consente di posizionare il middleware in una posizione arbitraria nella pipeline:
    • Gli endpoint sono eseguiti nella posizione di UseEndpoints.
  • Il middleware del terminale consente al codice arbitrario di determinare quando il middleware corrisponde:
    • Il codice personalizzato di corrispondenza della route può essere dettagliato e difficile da scrivere correttamente.
    • Il routing offre soluzioni semplici per le app tipiche. La maggior parte delle app non richiede codice di corrispondenza di route personalizzato.
  • Interfaccia degli endpoint con middleware, UseAuthorization ad esempio e UseCors.
    • L'uso di un middleware del terminale con UseAuthorization o UseCors richiede l'interfaccia manuale con il sistema di autorizzazione.

Un endpoint definisce entrambi:

  • Delegato per elaborare le richieste.
  • Raccolta di metadati arbitrari. I metadati vengono usati per implementare problematiche trasversali in base ai criteri e alla configurazione collegati a ogni endpoint.

Il middleware del terminale può essere uno strumento efficace, ma può richiedere:

  • Quantità significativa di codifica e test.
  • Integrazione manuale con altri sistemi per ottenere il livello di flessibilità desiderato.

Prendere in considerazione l'integrazione con il routing prima di scrivere un middleware del terminale.

Il middleware del terminale esistente che si integra con Map o MapWhen in genere può essere trasformato in un endpoint compatibile con il routing. MapHealthChecks illustra il modello per router-ware:

Il codice seguente illustra l'uso di MapHealthChecks:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

L'esempio precedente mostra perché la restituzione dell'oggetto builder è importante. La restituzione dell'oggetto builder consente allo sviluppatore di app di configurare criteri come l'autorizzazione per l'endpoint. In questo esempio, il middleware dei controlli di integrità non ha alcuna integrazione diretta con il sistema di autorizzazione.

Il sistema di metadati è stato creato in risposta ai problemi riscontrati dagli autori dell'estendibilità usando il middleware del terminale. È problematico per ogni middleware implementare la propria integrazione con il sistema di autorizzazione.

Corrispondenza URL

  • Processo in base al quale il routing corrisponde a una richiesta in ingresso a un endpoint.
  • Si basa sui dati nel percorso e nelle intestazioni URL.
  • Può essere esteso per prendere in considerazione tutti i dati nella richiesta.

Quando viene eseguito un middleware di routing, imposta un valore Endpoint e indirizza i valori a una funzionalità di richiesta nella HttpContext richiesta corrente:

  • La chiamata a HttpContext.GetEndpoint ottiene l'endpoint.
  • HttpRequest.RouteValues ottiene la raccolta dei valori di route.

Il middleware eseguito dopo il middleware di routing può esaminare l'endpoint e intervenire. Ad esempio, un middleware di autorizzazione può interrogare la raccolta di metadati dell'endpoint per un criterio di autorizzazione. Dopo l'esecuzione di tutto il middleware nella pipeline di elaborazione della richiesta, viene richiamato il delegato dell'endpoint selezionato.

Il sistema di routing nell'endpoint di routing è responsabile di tutte le decisioni relative all'invio. Poiché il middleware applica i criteri in base all'endpoint selezionato, è importante:

  • Qualsiasi decisione che può influire sull'invio o sull'applicazione dei criteri di sicurezza viene effettuata all'interno del sistema di routing.

Avviso

Per garantire la compatibilità con le versioni precedenti, quando viene eseguito un delegato dell'endpoint Controller o Razor Pages, le proprietà di RouteContext.RouteData vengono impostate sui valori appropriati in base all'elaborazione della richiesta eseguita finora.

Il RouteContext tipo verrà contrassegnato come obsoleto in una versione futura:

  • Eseguire la migrazione RouteData.Values a HttpRequest.RouteValues.
  • Eseguire la migrazione RouteData.DataTokens per recuperare IDataTokensMetadata dai metadati dell'endpoint.

La corrispondenza degli URL opera in un set configurabile di fasi. In ogni fase, l'output è un set di corrispondenze. Il set di corrispondenze può essere limitato ulteriormente dalla fase successiva. L'implementazione del routing non garantisce un ordine di elaborazione per gli endpoint corrispondenti. Tutte le possibili corrispondenze vengono elaborate contemporaneamente. Le fasi di corrispondenza dell'URL si verificano nell'ordine seguente. ASP.NET Core:

  1. Elabora il percorso URL rispetto al set di endpoint e ai relativi modelli di route, raccogliendo tutte le corrispondenze.
  2. Accetta l'elenco precedente e rimuove le corrispondenze che hanno esito negativo con vincoli di route applicati.
  3. Accetta l'elenco precedente e rimuove le corrispondenze che non superano il set di MatcherPolicy istanze.
  4. Usa per EndpointSelector prendere una decisione finale dall'elenco precedente.

L'elenco degli endpoint è prioritario in base a:

Tutti gli endpoint corrispondenti vengono elaborati in ogni fase fino a quando non viene raggiunto .EndpointSelector è EndpointSelector la fase finale. Sceglie l'endpoint con priorità più alta tra le corrispondenze come corrispondenza migliore. Se sono presenti altre corrispondenze con la stessa priorità della corrispondenza migliore, viene generata un'eccezione di corrispondenza ambigua.

La precedenza della route viene calcolata in base a un modello di route più specifico a cui viene assegnata una priorità più alta. Si considerino ad esempio i modelli /hello e /{message}:

  • Entrambi corrispondono al percorso /helloURL .
  • /hello è più specifico e quindi priorità più alta.

In generale, la precedenza di route fa un buon lavoro per scegliere la corrispondenza migliore per i tipi di schemi URL usati in pratica. Usare Order solo quando necessario per evitare ambiguità.

A causa dei tipi di estendibilità forniti dal routing, non è possibile che il sistema di routing calcoli in anticipo le route ambigue. Si consideri un esempio, ad esempio i modelli /{message:alpha} di route e /{message:int}:

  • Il alpha vincolo corrisponde solo ai caratteri alfabetici.
  • Il int vincolo corrisponde solo ai numeri.
  • Questi modelli hanno la stessa precedenza di route, ma non esiste un singolo URL che corrispondono entrambi.
  • Se il sistema di routing ha segnalato un errore di ambiguità all'avvio, blocca questo caso d'uso valido.

Avviso

L'ordine delle operazioni all'interno UseEndpoints non influisce sul comportamento del routing, con un'eccezione. MapControllerRoute e MapAreaRoute assegnano automaticamente un valore di ordine ai relativi endpoint in base all'ordine in cui vengono richiamati. Questo simula il comportamento a lungo termine dei controller senza che il sistema di routing fornisca le stesse garanzie delle implementazioni di routing meno recenti.

Endpoint routing in ASP.NET Core:

  • Non ha il concetto di itinerari.
  • Non fornisce garanzie di ordinamento. Tutti gli endpoint vengono elaborati contemporaneamente.

Precedenza del modello di route e ordine di selezione dell'endpoint

La precedenza del modello di route è un sistema che assegna a ogni modello di route un valore in base al modo in cui è specifico. Precedenza del modello di route:

  • Evita la necessità di modificare l'ordine degli endpoint nei casi comuni.
  • Tenta di corrispondere alle aspettative di buon senso del comportamento di routing.

Si considerino ad esempio modelli /Products/List e /Products/{id}. Sarebbe ragionevole presupporre che /Products/List sia una corrispondenza migliore rispetto /Products/{id} al percorso /Products/ListURL . Ciò funziona perché il segmento letterale /List viene considerato avere una precedenza migliore rispetto al segmento di /{id}parametro .

I dettagli sul funzionamento della precedenza sono associati alla definizione dei modelli di route:

  • I modelli con più segmenti sono considerati più specifici.
  • Un segmento con testo letterale è considerato più specifico di un segmento di parametro.
  • Un segmento di parametro con un vincolo viene considerato più specifico di uno senza.
  • Un segmento complesso viene considerato specifico come segmento di parametro con un vincolo.
  • I parametri catch-all sono i meno specifici. Per informazioni importanti sulle route catch-all, vedere catch-all nella sezione Modelli di route.

Concetti relativi alla generazione di URL

Generazione url:

  • Processo in base al quale il routing può creare un percorso URL basato su un set di valori di route.
  • Consente una separazione logica tra gli endpoint e gli URL che vi accedono.

Il routing degli endpoint include l'API LinkGenerator . LinkGeneratorè un servizio singleton disponibile dall'inserimento delle dipendenze. L'API LinkGenerator può essere usata al di fuori del contesto di una richiesta in esecuzione. Mvc.IUrlHelper e scenari che si basano su IUrlHelper, ad esempio helper tag, helper HTML e risultati azione, usano l'API LinkGenerator internamente per fornire funzionalità di generazione di collegamenti.

Il generatore di collegamenti si basa sui concetti di indirizzo e di schemi di indirizzi. Lo schema di indirizzi consente di determinare gli endpoint che devono essere considerati per la generazione dei collegamenti. Ad esempio, gli scenari di nome della route e valori di route con cui molti utenti hanno familiarità con i controller e Razor le pagine vengono implementati come schema di indirizzi.

Il generatore di collegamenti può collegarsi ai controller e Razor alle pagine tramite i metodi di estensione seguenti:

Gli overload di questi metodi accettano argomenti che includono .HttpContext Questi metodi sono funzionalmente equivalenti a Url.Action e Url.Page, ma offrono flessibilità e opzioni aggiuntive.

I GetPath* metodi sono più simili a Url.Action e Url.Page, in quanto generano un URI contenente un percorso assoluto. I metodi GetUri* generano sempre un URI assoluto contenente uno schema e un host. I metodi che accettano HttpContext generano un URI nel contesto della richiesta in esecuzione. I valori di route di ambiente , il percorso di base URL, lo schema e l'host della richiesta in esecuzione vengono usati a meno che non venga sottoposto a override.

LinkGenerator viene chiamato con un indirizzo. La generazione di un URI viene eseguita in due passaggi:

  1. Un indirizzo viene associato a un elenco di endpoint che corrispondono all'indirizzo.
  2. RoutePattern di ogni endpoint viene valutato fino a quando non viene individuato un formato di route che corrisponde ai valori specificati. L'output risultante viene unito alle altre parti dell'URI specificate nel generatore di collegamenti e restituito.

I metodi forniti da LinkGenerator supportano le funzionalità di generazione di collegamenti standard per tutti i tipi di indirizzi. Il modo più pratico per usare il generatore di collegamenti è tramite metodi di estensione che eseguono operazioni per un tipo di indirizzo specifico:

Metodo di estensione Descrizione
GetPathByAddress Genera un URI con un percorso assoluto in base ai valori specificati.
GetUriByAddress Genera un URI assoluto in base ai valori specificati.

Avviso

Prestare attenzione alle implicazioni seguenti della chiamata ai metodi LinkGenerator:

  • Usare i metodi di estensione GetUri* con cautela in una configurazione di app che non convalida l'intestazione Host delle richieste in ingresso. Se l'intestazione delle richieste in ingresso non viene convalidata, l'input Host di richiesta non attendibile può essere inviato al client negli URI di una visualizzazione o di una pagina. È consigliabile che in tutte le app di produzione il server sia configurato per la convalida dell'intestazione Host rispetto ai valori validi noti.

  • Usare LinkGenerator con cautela nel middleware in associazione a Map o MapWhen. Map* modifica il percorso di base della richiesta in esecuzione, che ha effetto sull'output della generazione di collegamenti. Tutte le API LinkGenerator consentono di specificare un percorso di base. Specificare un percorso di base vuoto per annullare l'effetto Map* sulla generazione del collegamento.

Esempio di middleware

Nell'esempio seguente un middleware usa l'API LinkGenerator per creare un collegamento a un metodo di azione che elenca i prodotti dell'archivio. L'uso del generatore di collegamenti inserendolo in una classe e chiamando GenerateLink è disponibile per qualsiasi classe in un'app:

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

Modelli di route

I token all'interno {} definiscono i parametri di route associati se la route corrisponde. È possibile definire più parametri di route in un segmento di route, ma i parametri di route devono essere separati da un valore letterale. Ad esempio:

{controller=Home}{action=Index}

non è una route valida, perché non esiste alcun valore letterale tra {controller} e {action}. I parametri di route devono avere un nome e possono avere attributi aggiuntivi specificati.

Il testo letterale diverso dai parametri di route, ad esempio {id}, e il separatore di percorso / devono corrispondere al testo nell'URL. La corrispondenza del testo non fa distinzione tra maiuscole e minuscole e si basa sulla rappresentazione decodificata del percorso dell'URL. Per trovare una corrispondenza con un delimitatore { di parametro di route letterale o }, eseguire l'escape del delimitatore ripetendo il carattere. Ad esempio {{ o }}.

Asterisco * o asterisco **doppio:

  • Può essere usato come prefisso per un parametro di route da associare all'URI rest .
  • Vengono chiamati parametri catch-all . Ad esempio, blog/{**slug}:
    • Trova la corrispondenza con qualsiasi URI che inizia con blog/ e ha qualsiasi valore dopo di esso.
    • Il valore seguente blog/ viene assegnato al valore di route slug .

Avviso

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

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

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

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

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

I parametri catch-all possono anche corrispondere alla stringa vuota.

Il parametro catch-all esegue l'escape dei caratteri appropriati quando viene usata la route per generare un URL, inclusi i caratteri separatori / di percorso. Ad esempio, la route foo/{*path} con i valori di route { path = "my/path" } genera foo/my%2Fpath. Si noti la barra di escape. Per eseguire il round trip dei caratteri di separatore di percorso, usare il prefisso del parametro di route **. La route foo/{**path} con { path = "my/path" } genera foo/my/path.

I modelli di URL che tentano di acquisire un nome file con un'estensione facoltativa hanno considerazioni aggiuntive. Considerare ad esempio il modello files/{filename}.{ext?}. Se esistono i valori per filename e ext, vengono popolati entrambi i valori. Se nell'URL esiste solo un valore per filename , la route corrisponde perché l'elemento finale . è facoltativo. Gli URL seguenti corrispondono a questa route:

  • /files/myFile.txt
  • /files/myFile

I parametri di route possono avere valori predefiniti, definiti specificando il valore predefinito dopo il nome del parametro, separato da un segno di uguale (=). Ad esempio, {controller=Home} definisce Home come valore predefinito per controller. Il valore predefinito viene usato se nell'URL non è presente alcun valore per il parametro. I parametri di route vengono resi facoltativi aggiungendo un punto interrogativo (?) alla fine del nome del parametro. Ad esempio: id?. La differenza tra i valori facoltativi e i parametri di route predefiniti è:

  • Un parametro di route con un valore predefinito produce sempre un valore.
  • Un parametro facoltativo ha un valore solo quando un valore viene fornito dall'URL della richiesta.

I parametri di route possono presentare dei vincoli che devono corrispondere al valore di route associato dall'URL. L'aggiunta : del nome del vincolo e dopo il nome del parametro di route specifica un vincolo inline su un parametro di route. Se il vincolo richiede argomenti, vengono racchiusi tra parentesi (...) dopo il nome del vincolo. È possibile specificare più vincoli inline aggiungendo un altro : nome di vincolo e .

Il nome del vincolo e gli argomenti vengono passati al servizio IInlineConstraintResolver per creare un'istanza di IRouteConstraint da usare nell'elaborazione dell'URL. Il modello di route blog/{article:minlength(10)} specifica ad esempio un vincolo minlength con l'argomento 10. Per altre informazioni sui vincoli di route e un elenco dei vincoli forniti dal framework, vedere la sezione Vincoli di route.

I parametri di route possono avere anche trasformatori di parametro. I trasformatori di parametro trasformano il valore di un parametro durante la generazione di collegamenti e azioni e pagine corrispondenti agli URL. Analogamente ai vincoli, i trasformatori di parametro possono essere aggiunti inline a un parametro di route aggiungendo un : nome di trasformatore e dopo il nome del parametro di route. Ad esempio, il modello di route blog/{article:slugify} specifica un trasformatore slugify. Per altre informazioni sui trasformatori di parametri, vedere la sezione Trasformatori di parametro.

La tabella seguente illustra modelli di route di esempio e il relativo comportamento:

Modello di route URI corrispondente di esempio L'URI della richiesta
hello /hello Verifica la corrispondenza solo del singolo percorso /hello.
{Page=Home} / Verifica la corrispondenza e imposta Page su Home.
{Page=Home} /Contact Verifica la corrispondenza e imposta Page su Contact.
{controller}/{action}/{id?} /Products/List Esegue il mapping al controller Products e all'azione List.
{controller}/{action}/{id?} /Products/Details/123 Esegue il Products mapping al controller e Details all'azione conid impostato su 123.
{controller=Home}/{action=Index}/{id?} / Esegue il mapping al controller e Index al Home metodo . id viene ignorato.
{controller=Home}/{action=Index}/{id?} /Products Esegue il mapping al controller e Index al Products metodo . id viene ignorato.

L'uso di un modello è in genere l'approccio più semplice al routing. I vincoli e le impostazioni predefinite possono essere specificati anche all'esterno del modello di route.

Segmenti complessi

I segmenti complessi vengono elaborati associando delimitatori letterali da destra a sinistra in modo non greedy . Ad esempio, [Route("/a{b}c{d}")] è un segmento complesso. I segmenti complessi funzionano in modo particolare che devono essere compresi per usarli correttamente. L'esempio in questa sezione illustra perché i segmenti complessi funzionano correttamente solo quando il testo del delimitatore non viene visualizzato all'interno dei valori dei parametri. L'uso di un'espressione regolare e quindi l'estrazione manuale dei valori è necessaria per casi più complessi.

Avviso

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

Si tratta di un riepilogo dei passaggi che il routing esegue con il modello /a{b}c{d} e il percorso /abcdURL . Viene | usato per visualizzare il funzionamento dell'algoritmo:

  • Il primo valore letterale, da destra a sinistra, è c. Quindi /abcd viene eseguita la ricerca da destra e trova /ab|c|d.
  • Tutti gli elementi a destra (d) vengono ora confrontati con il parametro {d}di route .
  • Il valore letterale successivo, da destra a sinistra, è a. Quindi /ab|c|d viene eseguita una ricerca a partire dal punto in cui è stato interrotto, quindi a viene trovato /|a|b|c|d.
  • Il valore a destra (b) viene ora confrontato con il parametro {b}di route .
  • Nessun testo rimanente e nessun modello di route rimanente, quindi si tratta di una corrispondenza.

Di seguito è riportato un esempio di caso negativo usando lo stesso modello /a{b}c{d} e il percorso /aabcdURL . Viene | usato per visualizzare il funzionamento dell'algoritmo. Questo caso non è una corrispondenza, spiegata dallo stesso algoritmo:

  • Il primo valore letterale, da destra a sinistra, è c. Quindi /aabcd viene eseguita la ricerca da destra e trova /aab|c|d.
  • Tutti gli elementi a destra (d) vengono ora confrontati con il parametro {d}di route .
  • Il valore letterale successivo, da destra a sinistra, è a. Quindi /aab|c|d viene eseguita una ricerca a partire dal punto in cui è stato interrotto, quindi a viene trovato /a|a|b|c|d.
  • Il valore a destra (b) viene ora confrontato con il parametro {b}di route .
  • A questo punto è presente testo arimanente, ma l'algoritmo ha esaurito il modello di route da analizzare, quindi non si tratta di una corrispondenza.

Poiché l'algoritmo corrispondente non è greedy:

  • Corrisponde alla quantità minima di testo possibile in ogni passaggio.
  • Ogni caso in cui il valore del delimitatore venga visualizzato all'interno dei valori dei parametri non corrisponde.

Le espressioni regolari offrono un maggiore controllo sul comportamento di corrispondenza.

Corrispondenza Greedy, nota anche come tentativi di corrispondenza massima per trovare la corrispondenza più lunga possibile nel testo di input che soddisfa il modello regex . La corrispondenza non greedy, nota anche come corrispondenza differita, cerca la corrispondenza più breve possibile nel testo di input che soddisfa il modello regex.

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;

Vincoli della route

I vincoli di route vengono eseguiti quando si verifica una corrispondenza nell'URL in ingresso e il percorso URL viene suddiviso in valori di route in formato token. I vincoli di route in genere controllano il valore di route associato tramite il modello di route e prendere una decisione vera o falsa circa se il valore è accettabile. Alcuni vincoli di route usano i dati all'esterno del valore di route per stabilire se la richiesta può essere instradata. Ad esempio, HttpMethodRouteConstraint può accettare o rifiutare una richiesta in base al relativo verbo HTTP. I vincoli vengono usati nelle richieste del routing e nella generazione di collegamenti.

Avviso

Non usare i vincoli per la convalida dell'input. Se i vincoli vengono usati per la convalida dell'input, l'input non valido restituisce una 404 risposta non trovata. L'input non valido deve produrre una 400 richiesta non valida con un messaggio di errore appropriato. I vincoli di route vengono usati per evitare ambiguità tra route simili, non per convalidare gli input per una route specifica.

La tabella seguente illustra i vincoli di route di esempio e il relativo comportamento previsto:

vincolo Esempio Esempi di corrispondenza Note
int {id:int} 123456789, -123456789 Corrisponde a qualsiasi numero intero
bool {active:bool} true, FALSE Corrisponde true a o false. Nessuna distinzione tra maiuscole e minuscole
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Corrisponde a un valore valido DateTime nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
decimal {price:decimal} 49.99, -1,000.01 Corrisponde a un valore valido decimal nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
double {weight:double} 1.234, -1,001.01e8 Corrisponde a un valore valido double nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
float {weight:float} 1.234, -1,001.01e8 Corrisponde a un valore valido float nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Corrisponde a un valore Guid valido
long {ticks:long} 123456789, -123456789 Corrisponde a un valore long valido
minlength(value) {username:minlength(4)} Rick La stringa deve contenere almeno 4 caratteri
maxlength(value) {filename:maxlength(8)} MyFile La stringa non deve contenere più di 8 caratteri
length(length) {filename:length(12)} somefile.txt La stringa deve contenere esattamente 12 caratteri
length(min,max) {filename:length(8,16)} somefile.txt La stringa deve contenere almeno 8 e non più di 16 caratteri
min(value) {age:min(18)} 19 Il valore intero deve essere almeno 18
max(value) {age:max(120)} 91 Il valore intero non deve essere superiore a 120
range(min,max) {age:range(18,120)} 91 Il valore intero deve essere almeno 18 ma non più di 120
alpha {name:alpha} Rick La stringa deve essere costituita da uno o più caratteri a-z alfabetici e senza distinzione tra maiuscole e minuscole.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 La stringa deve corrispondere all'espressione regolare. Vedere suggerimenti sulla definizione di un'espressione regolare.
required {name:required} Rick Usato per imporre che un valore diverso da un parametro sia presente durante la generazione dell'URL

Avviso

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

Più vincoli delimitati da due punti possono essere applicati a un singolo parametro. Ad esempio il vincolo seguente limita un parametro a un valore intero maggiore o uguale a 1:

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Avviso

I vincoli di route che verificano l'URL e vengono convertiti in un tipo CLR usano sempre le impostazioni cultura invarianti. Ad esempio, la conversione nel tipo int CLR o DateTime. Questi vincoli presuppongono che l'URL non sia localizzabile. I vincoli di route specificati dal framework non modificano i valori archiviati nei valori di route. Tutti i valori di route analizzati dall'URL vengono archiviati come stringhe. Ad esempio, il vincolo float prova a convertire il valore di route in un valore float, ma il valore convertito viene usato solo per verificare che può essere convertito in un valore float.

Espressioni regolari nei vincoli

Avviso

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

Le espressioni regolari possono essere specificate come vincoli inline usando il vincolo di regex(...) route. I metodi nella MapControllerRoute famiglia accettano anche un valore letterale oggetto di vincoli. Se viene utilizzata la maschera, i valori stringa vengono interpretati come espressioni regolari.

Il codice seguente usa un vincolo regex inline:

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

Il codice seguente usa un valore letterale oggetto per specificare un vincolo regex:

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

Il framework di ASP.NET Core aggiunge RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant al costruttore di espressioni regolari. Per una descrizione di questi membri, vedere RegexOptions.

Le espressioni regolari usano delimitatori e token simili a quelli usati dal routing e dal linguaggio C#. Per i token di espressione è necessario aggiungere caratteri di escape. Per usare l'espressione ^\d{3}-\d{2}-\d{4}$ regolare in un vincolo inline, usare una delle opzioni seguenti:

  • Sostituire i \ caratteri specificati nella stringa come \\ caratteri nel file di origine C# per eseguire l'escape del carattere di escape della \ stringa.
  • Valori letterali stringa verbatim.

Per eseguire l'escape dei caratteri {delimitatori dei parametri di routing , [}, , ]double i caratteri nell'espressione, ad esempio , [[{{}}, , . ]] La tabella seguente mostra un'espressione regolare e la relativa versione con escape:

Espressione regolare Espressione regolare preceduta da escape
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Le espressioni regolari usate nel routing spesso iniziano con il ^ carattere e corrispondono alla posizione iniziale della stringa. Le espressioni terminano spesso con il $ carattere e corrispondono alla fine della stringa. I ^ caratteri e $ assicurano che l'espressione regolare corrisponda all'intero valore del parametro di route. Senza i ^ caratteri e $ , l'espressione regolare corrisponde a qualsiasi sottostringa all'interno della stringa, spesso indesiderata. La tabella seguente fornisce esempi e spiega il motivo per cui corrispondono o non corrispondono:

Espressione String Match Commento
[a-z]{2} hello Corrispondenze di sottostringhe
[a-z]{2} 123abc456 Corrispondenze di sottostringhe
[a-z]{2} mz Corrisponde all'espressione
[a-z]{2} MZ Senza distinzione maiuscole/minuscole
^[a-z]{2}$ hello No Vedere ^ e $ sopra
^[a-z]{2}$ 123abc456 No Vedere ^ e $ sopra

Per altre informazioni sulla sintassi delle espressioni regolari, vedere Espressioni regolari di .NET Framework.

Per limitare un parametro a un set noto di valori possibili, usare un'espressione regolare. Ad esempio, {action:regex(^(list|get|create)$)} verifica la corrispondenza del valore di route action solo con list, get o create. Se viene passata nel dizionario di vincoli, la stringa ^(list|get|create)$ è equivalente. I vincoli passati nel dizionario vincoli che non corrispondono a uno dei vincoli noti vengono considerati anche come espressioni regolari. I vincoli passati all'interno di un modello che non corrispondono a uno dei vincoli noti non vengono considerati come espressioni regolari.

Vincoli di route personalizzati

È possibile creare vincoli di route personalizzati implementando l'interfaccia IRouteConstraint . L'interfaccia IRouteConstraint contiene Match, che restituisce true se il vincolo è soddisfatto e false in caso contrario.

I vincoli di route personalizzati sono raramente necessari. Prima di implementare un vincolo di route personalizzato, prendere in considerazione alternative, ad esempio l'associazione di modelli.

La cartella ASP.NET vincoli principali fornisce esempi validi di creazione di vincoli. Ad esempio, GuidRouteConstraint.

Per usare un oggetto personalizzato IRouteConstraint, il tipo di vincolo di route deve essere registrato con l'app ConstraintMap nel contenitore del servizio. ConstraintMap è un dizionario che esegue il mapping delle chiavi dei vincoli di route alle implementazioni di IRouteConstraint che convalidano tali vincoli. Un'app ConstraintMap può essere aggiornata in Program.cs come parte di una AddRouting chiamata o configurando RouteOptions direttamente con builder.Services.Configure<RouteOptions>. Ad esempio:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

Il vincolo precedente viene applicato nel codice seguente:

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

L'implementazione di NoZeroesRouteConstraint impedisce l'uso 0 in un parametro di route:

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Avviso

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

Il codice precedente:

  • Impedisce 0 nel {id} segmento della route.
  • Viene illustrato come fornire un esempio di base dell'implementazione di un vincolo personalizzato. Non deve essere usato in un'app di produzione.

Il codice seguente è un approccio migliore per impedire l'elaborazione di un oggetto id contenente un 0 oggetto :

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

Il codice precedente presenta i vantaggi seguenti rispetto all'approccio NoZeroesRouteConstraint :

  • Non richiede un vincolo personalizzato.
  • Restituisce un errore più descrittivo quando il parametro di route include 0.

Trasformatori di parametro

I trasformatori di parametro:

Ad esempio, un trasformatore di parametro slugify personalizzato nel modello di route blog\{article:slugify} con Url.Action(new { article = "MyTestArticle" }) genera blog\my-test-article.

Si consideri l'implementazione seguente IOutboundParameterTransformer:

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value is null)
        {
            return null;
        }

        return Regex.Replace(
            value.ToString()!,
                "([a-z])([A-Z])",
            "$1-$2",
            RegexOptions.CultureInvariant,
            TimeSpan.FromMilliseconds(100))
            .ToLowerInvariant();
    }
}

Per usare un trasformatore di parametro in un modello di route, configurarlo usando ConstraintMap in Program.cs:

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

Il framework ASP.NET Core usa trasformatori di parametro per trasformare l'URI in cui viene risolto un endpoint. Ad esempio, i trasformatori di parametro trasformano i valori di route usati per trovare la corrispondenza con un areaoggetto , controlleraction, e page:

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

Con il modello di route precedente, l'azione SubscriptionManagementController.GetAll viene confrontata con l'URI /subscription-management/get-all. Un trasformatore di parametro non modifica i valori della route usati per generare un collegamento. Ad esempio, Url.Action("GetAll", "SubscriptionManagement") restituisce /subscription-management/get-all.

ASP.NET Core fornisce convenzioni API per l'uso di trasformatori di parametri con route generate:

Riferimento per la generazione di URL

Questa sezione contiene un riferimento per l'algoritmo implementato dalla generazione di URL. In pratica, esempi più complessi di generazione url usano controller o Razor pagine. Per altre informazioni, vedere Routing nei controller .

Il processo di generazione url inizia con una chiamata a LinkGenerator.GetPathByAddress o un metodo simile. Il metodo viene fornito con un indirizzo, un set di valori di route e facoltativamente informazioni sulla richiesta corrente da HttpContext.

Il primo passaggio consiste nell'usare l'indirizzo per risolvere un set di endpoint candidati usando un oggetto IEndpointAddressScheme<TAddress> che corrisponde al tipo di indirizzo.

Dopo che il set di candidati viene trovato dallo schema di indirizzi, gli endpoint vengono ordinati ed elaborati in modo iterativo fino a quando non viene completata un'operazione di generazione url. La generazione di URL non verifica la presenza di ambiguità, il primo risultato restituito è il risultato finale.

Risoluzione dei problemi relativi alla generazione di URL con la registrazione

Il primo passaggio per la risoluzione dei problemi relativi alla generazione di URL consiste nell'impostare il livello di registrazione di Microsoft.AspNetCore.Routing su TRACE. LinkGenerator registra molti dettagli sull'elaborazione che può essere utile per risolvere i problemi.

Per informazioni dettagliate sulla generazione di URL, vedere Informazioni di riferimento sulla generazione di URL.

Indirizzi

Gli indirizzi sono il concetto nella generazione di URL usato per associare una chiamata al generatore di collegamenti a un set di endpoint candidati.

Gli indirizzi sono un concetto estendibile che include due implementazioni per impostazione predefinita:

  • Uso del nome dell'endpoint (string) come indirizzo:
    • Fornisce funzionalità simili al nome della route di MVC.
    • Usa il IEndpointNameMetadata tipo di metadati.
    • Risolve la stringa fornita rispetto ai metadati di tutti gli endpoint registrati.
    • Genera un'eccezione all'avvio se più endpoint usano lo stesso nome.
    • Consigliato per l'uso generico all'esterno di controller e Razor pagine.
  • Uso dei valori di route (RouteValuesAddress) come indirizzo:
    • Fornisce funzionalità simili ai controller e Razor alla generazione di URL legacy di Pages.
    • Molto complesso da estendere ed eseguire il debug.
    • Fornisce l'implementazione usata da IUrlHelper, helper tag, helper HTML, risultati azione e così via.

Il ruolo dello schema di indirizzi consiste nell'eseguire l'associazione tra l'indirizzo e gli endpoint corrispondenti in base a criteri arbitrari:

  • Lo schema dei nomi dell'endpoint esegue una ricerca di dizionario di base.
  • Lo schema dei valori di route ha un subset migliore complesso dell'algoritmo set.

Valori di ambiente e valori espliciti

Dalla richiesta corrente, il routing accede ai valori di route della richiesta HttpContext.Request.RouteValuescorrente. I valori associati alla richiesta corrente vengono definiti valori di ambiente. Ai fini della chiarezza, la documentazione fa riferimento ai valori di route passati ai metodi come valori espliciti.

Nell'esempio seguente vengono illustrati i valori di ambiente e i valori espliciti. Fornisce i valori di ambiente della richiesta corrente e dei valori espliciti:

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

Il codice precedente:

Il codice seguente fornisce solo valori espliciti e nessun valore di ambiente:

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

Il metodo precedente restituisce /Home/Subscribe/17

Il codice seguente in WidgetController restituisce /Widget/Subscribe/17:

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

Il codice seguente fornisce il controller dai valori di ambiente nella richiesta corrente e nei valori espliciti:

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

Nel codice precedente:

  • /Gadget/Edit/17 viene restituito.
  • Url ottiene l'oggetto IUrlHelper.
  • Action genera un URL con un percorso assoluto per un metodo di azione. L'URL contiene il nome e route i valori specificatiaction.

Il codice seguente fornisce i valori di ambiente della richiesta corrente e dei valori espliciti:

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

Il codice precedente imposta su url /Edit/17 quando la pagina di modifica Razor contiene la direttiva di pagina seguente:

@page "{id:int}"

Se la pagina Modifica non contiene il "{id:int}" modello di route, url è /Edit?id=17.

Il comportamento di MVC IUrlHelper aggiunge un livello di complessità oltre alle regole descritte di seguito:

  • IUrlHelper fornisce sempre i valori di route della richiesta corrente come valori di ambiente.
  • IUrlHelper.Action copia sempre i valori correnti action e controller di route come valori espliciti, a meno che non venga sottoposto a override dallo sviluppatore.
  • IUrlHelper.Page copia sempre il valore di route corrente page come valore esplicito, a meno che non venga sottoposto a override.
  • IUrlHelper.Page esegue sempre l'override del valore di route corrente handler con null come valori espliciti, a meno che non venga sottoposto a override.

Gli utenti sono spesso sorpresi dai dettagli comportamentali dei valori di ambiente, perché MVC non sembra seguire le proprie regole. Per motivi cronologici e di compatibilità, alcuni valori di route, actionad esempio , controllerpage, e handler hanno un comportamento specifico.

La funzionalità equivalente fornita da LinkGenerator.GetPathByAction e LinkGenerator.GetPathByPage duplica queste anomalie di IUrlHelper per la compatibilità.

Processo di generazione url

Dopo aver trovato il set di endpoint candidati, l'algoritmo di generazione url:

  • Elabora gli endpoint in modo iterativo.
  • Restituisce il primo risultato riuscito.

Il primo passaggio di questo processo è denominato invalidazione del valore di route. L'invalidazione del valore di route è il processo in base al quale il routing decide quali valori di route dai valori di ambiente devono essere usati e quali devono essere ignorati. Ogni valore di ambiente viene considerato e combinato con i valori espliciti o ignorato.

Il modo migliore per considerare il ruolo dei valori di ambiente è che tentano di salvare gli sviluppatori di applicazioni digitando, in alcuni casi comuni. Tradizionalmente, gli scenari in cui i valori di ambiente sono utili sono correlati a MVC:

  • Quando si esegue il collegamento a un'altra azione nello stesso controller, non è necessario specificare il nome del controller.
  • Quando si esegue il collegamento a un altro controller nella stessa area, non è necessario specificare il nome dell'area.
  • Quando si esegue il collegamento allo stesso metodo di azione, non è necessario specificare i valori di route.
  • Quando si esegue il collegamento a un'altra parte dell'app, non si vogliono trasportare valori di route che non hanno alcun significato in tale parte dell'app.

Le chiamate a LinkGenerator o IUrlHelper che restituiscono null sono in genere causate dalla mancata comprensione dell'invalidazione del valore di route. Risolvere i problemi di invalidazione del valore di route specificando in modo esplicito più valori di route per verificare se questo risolve il problema.

L'invalidazione del valore di route si basa sul presupposto che lo schema URL dell'app sia gerarchico, con una gerarchia formata da sinistra a destra. Si consideri il modello {controller}/{action}/{id?} di route del controller di base per avere un'idea intuitiva del funzionamento di questa operazione in pratica. Una modifica a un valore invalida tutti i valori di route visualizzati a destra. Ciò riflette il presupposto sulla gerarchia. Se l'app ha un valore di ambiente per ide l'operazione specifica un valore diverso per :controller

  • id non verrà riutilizzato perché {controller} si trova a sinistra di {id?}.

Alcuni esempi che illustrano questo principio:

  • Se i valori espliciti contengono un valore per id, il valore di ambiente per id viene ignorato. I valori di ambiente per controller e action possono essere usati.
  • Se i valori espliciti contengono un valore per action, qualsiasi valore di ambiente per action viene ignorato. È possibile usare i valori di ambiente per controller . Se il valore esplicito per action è diverso dal valore di ambiente per action, il id valore non verrà usato. Se il valore esplicito per action è uguale al valore di ambiente per action, è possibile usare il id valore .
  • Se i valori espliciti contengono un valore per controller, qualsiasi valore di ambiente per controller viene ignorato. Se il valore esplicito per controller è diverso dal valore di ambiente per controller, i action valori e id non verranno usati. Se il valore esplicito per controller è uguale al valore di ambiente per controller, è possibile usare i action valori e id .

Questo processo è ulteriormente complicato dall'esistenza di route di attributi e route convenzionali dedicate. Route convenzionali del controller, ad {controller}/{action}/{id?} esempio specificare una gerarchia usando i parametri di route. Per route convenzionali dedicate e route di attributi ai controller e Razor alle pagine:

  • Esiste una gerarchia di valori di route.
  • Non vengono visualizzati nel modello.

Per questi casi, la generazione di URL definisce il concetto di valori necessari. Gli endpoint creati dai controller e Razor pagine hanno valori obbligatori specificati che consentono il funzionamento dell'invalidazione del valore di route.

Algoritmo di invalidazione del valore della route in dettaglio:

  • I nomi dei valori obbligatori vengono combinati con i parametri di route, quindi elaborati da sinistra a destra.
  • Per ogni parametro vengono confrontati il valore di ambiente e il valore esplicito:
    • Se il valore di ambiente e il valore esplicito sono uguali, il processo continua.
    • Se il valore di ambiente è presente e il valore esplicito non è , il valore di ambiente viene usato durante la generazione dell'URL.
    • Se il valore di ambiente non è presente e il valore esplicito è , rifiutare il valore di ambiente e tutti i valori di ambiente successivi.
    • Se sono presenti il valore di ambiente e il valore esplicito e i due valori sono diversi, rifiutare il valore di ambiente e tutti i valori di ambiente successivi.

A questo punto, l'operazione di generazione url è pronta per valutare i vincoli di route. Il set di valori accettati viene combinato con i valori predefiniti del parametro, forniti ai vincoli. Se tutti i vincoli vengono passati, l'operazione continua.

Successivamente, i valori accettati possono essere usati per espandere il modello di route. Il modello di route viene elaborato:

  • Da sinistra a destra.
  • Ogni parametro ha il valore accettato sostituito.
  • Con i casi speciali seguenti:
    • Se i valori accettati mancano un valore e il parametro ha un valore predefinito, viene usato il valore predefinito.
    • Se i valori accettati mancano un valore e il parametro è facoltativo, l'elaborazione continua.
    • Se un parametro di route a destra di un parametro facoltativo mancante ha un valore, l'operazione ha esito negativo.
    • I parametri con valori predefiniti contigui e i parametri facoltativi vengono compressi laddove possibile.

I valori specificati in modo esplicito che non corrispondono a un segmento della route vengono aggiunti alla stringa di query. La tabella seguente illustra il risultato ottenuto quando si usa il modello di route {controller}/{action}/{id?}.

Valori di ambiente Valori espliciti Risultato
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", colore = "Rosso" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

Ordine dei parametri di route facoltativo

I parametri di route facoltativi devono venire dopo tutti i parametri di route e i valori letterali necessari. Nel codice seguente i id parametri e name devono provenire dopo il color parametro :

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[Route("api/[controller]")]
public class MyController : ControllerBase
{
    // GET /api/my/red/2/joe
    // GET /api/my/red/2
    // GET /api/my
    [HttpGet("{color}/{id:int?}/{name?}")]
    public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
    {
        return Ok($"{color} {id} {name ?? ""}");
    }
}

Problemi con l'invalidazione del valore di route

Il codice seguente mostra un esempio di schema di generazione url non supportato dal routing:

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

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

Nel codice precedente il parametro di route viene usato per la culture localizzazione. Il desiderio è avere il culture parametro sempre accettato come valore di ambiente. Tuttavia, il culture parametro non viene accettato come valore di ambiente a causa del funzionamento dei valori richiesti:

  • "default" Nel modello di route il culture parametro di route si trova a sinistra di controller, quindi le modifiche apportate a controller non invalidano culture.
  • "blog" Nel modello di route il culture parametro di route viene considerato a destra di controller, che viene visualizzato nei valori necessari.

Analizzare i percorsi URL con LinkParser

La LinkParser classe aggiunge il supporto per l'analisi di un percorso URL in un set di valori di route. Il ParsePathByEndpointName metodo accetta un nome di endpoint e un percorso URL e restituisce un set di valori di route estratti dal percorso URL.

Nel controller di esempio seguente, l'azione GetProduct usa un modello di route di api/Products/{id} e ha un Name valore di GetProduct:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

Nella stessa classe controller, l'azione AddRelatedProduct prevede un percorso URL, pathToRelatedProduct, che può essere fornito come parametro di stringa di query:

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

Nell'esempio precedente l'azione AddRelatedProduct estrae il valore di id route dal percorso URL. Ad esempio, con un percorso URL di /api/Products/1, il relatedProductId valore è impostato su 1. Questo approccio consente ai client dell'API di usare i percorsi URL quando si fa riferimento alle risorse, senza richiedere la conoscenza della struttura di tale URL.

Configurare i metadati dell'endpoint

I collegamenti seguenti forniscono informazioni su come configurare i metadati dell'endpoint:

Corrispondenza dell'host nelle route con RequireHost

RequireHost applica un vincolo alla route che richiede l'host specificato. Il RequireHost parametro o [Host] può essere:

  • Host: www.domain.com, corrisponde www.domain.com a qualsiasi porta.
  • Host con carattere jolly: *.domain.com, corrisponde www.domain.coma , subdomain.domain.como www.subdomain.domain.com su qualsiasi porta.
  • Porta: *:5000, corrisponde alla porta 5000 con qualsiasi host.
  • Host e porta: www.domain.com:5000 o *.domain.com:5000, corrisponde all'host e alla porta.

È possibile specificare più parametri usando RequireHost o [Host]. Il vincolo corrisponde agli host validi per uno dei parametri. Ad esempio, [Host("domain.com", "*.domain.com")] corrisponde a domain.com, www.domain.come subdomain.domain.com.

Il codice seguente usa RequireHost per richiedere l'host specificato nella route:

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

Il codice seguente usa l'attributo [Host] nel controller per richiedere uno degli host specificati:

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

Quando l'attributo [Host] viene applicato sia al controller che al metodo di azione:

  • Viene usato l'attributo sull'azione.
  • L'attributo controller viene ignorato.

Avviso

L'API che si basa sull'intestazione Host, ad esempio HttpRequest.Host e RequireHost, è soggetta a potenziali spoofing da parte dei client.

Per evitare lo spoofing di host e porta, usare uno degli approcci seguenti:

Gruppi di route

Il MapGroup metodo di estensione consente di organizzare gruppi di endpoint con un prefisso comune. Riduce il codice ripetitivo e consente di personalizzare interi gruppi di endpoint con una singola chiamata a metodi come RequireAuthorization e WithMetadata che aggiungono metadati dell'endpoint.

Ad esempio, il codice seguente crea due gruppi simili di endpoint:

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

In questo scenario è possibile usare un indirizzo relativo per l'intestazione Location nel 201 Created risultato:

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

Il primo gruppo di endpoint corrisponderà solo alle richieste precedute da /public/todos e sono accessibili senza alcuna autenticazione. Il secondo gruppo di endpoint corrisponderà solo alle richieste precedute /private/todos da e richiederanno l'autenticazione.

La QueryPrivateTodos factory del filtro endpoint è una funzione locale che modifica i parametri del TodoDb gestore di route per consentire l'accesso e l'archiviazione di dati todo privati.

I gruppi di route supportano anche gruppi annidati e modelli di prefisso complessi con parametri e vincoli di route. Nell'esempio seguente e il gestore di route mappato al user gruppo possono acquisire i {org} parametri e {group} di route definiti nei prefissi del gruppo esterno.

Il prefisso può anche essere vuoto. Ciò può essere utile per l'aggiunta di metadati o filtri dell'endpoint a un gruppo di endpoint senza modificare il modello di route.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

L'aggiunta di filtri o metadati a un gruppo ha lo stesso comportamento dell'aggiunta singolarmente a ogni endpoint prima di aggiungere altri filtri o metadati che potrebbero essere stati aggiunti a un gruppo interno o a un endpoint specifico.

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

Nell'esempio precedente, il filtro esterno registra la richiesta in ingresso prima del filtro interno anche se è stato aggiunto secondo. Poiché i filtri sono stati applicati a gruppi diversi, l'ordine in cui sono stati aggiunti l'uno rispetto all'altro non è importante. I filtri dell'ordine vengono aggiunti se applicati allo stesso gruppo o allo stesso endpoint specifico.

Una richiesta per /outer/inner/ registrare quanto segue:

/outer group filter
/inner group filter
MapGet filter

Indicazioni sulle prestazioni per il routing

Quando un'app presenta problemi di prestazioni, il routing viene spesso sospettato come problema. Il routing è sospetto che framework come controller e Razor pagine segnalano la quantità di tempo impiegato all'interno del framework nei messaggi di registrazione. Quando si verifica una differenza significativa tra il tempo segnalato dai controller e il tempo totale della richiesta:

  • Gli sviluppatori eliminano il codice dell'app come origine del problema.
  • È comune presupporre che il routing sia la causa.

Il routing viene testato usando migliaia di endpoint. È improbabile che un'app tipica incontri un problema di prestazioni semplicemente essendo troppo grande. La causa radice più comune delle prestazioni di routing lente è in genere un middleware personalizzato con comportamento non valido.

Questo esempio di codice seguente illustra una tecnica di base per restringere l'origine del ritardo:

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

Routing temporizzato:

  • Interleave ogni middleware con una copia del middleware di intervallo illustrato nel codice precedente.
  • Aggiungere un identificatore univoco per correlare i dati di intervallo con il codice.

Si tratta di un modo di base per limitare il ritardo quando è significativo, ad esempio, più di 10ms. Sottraendo Time 2 dai Time 1 report il tempo trascorso all'interno del UseRouting middleware.

Il codice seguente usa un approccio più compatto al codice di intervallo precedente:

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

Funzionalità di routing potenzialmente costose

L'elenco seguente fornisce alcune informazioni dettagliate sulle funzionalità di routing relativamente costose rispetto ai modelli di route di base:

  • Espressioni regolari: è possibile scrivere espressioni regolari complesse o con tempi di esecuzione lunghi con una piccola quantità di input.
  • Segmenti complessi ({x}-{y}-{z}):
    • Sono notevolmente più costosi rispetto all'analisi di un segmento di percorso URL normale.
    • Comporta l'allocazione di molte più sottostringhe.
  • Accesso ai dati sincrono: molte app complesse hanno accesso al database come parte del routing. Usare punti di estendibilità come MatcherPolicy e EndpointSelectorContext, che sono asincroni.

Linee guida per tabelle di route di grandi dimensioni

Per impostazione predefinita, ASP.NET Core usa un algoritmo di routing che scambia la memoria per il tempo della CPU. Questo ha l'effetto interessante che il tempo di corrispondenza della route dipende solo dalla lunghezza del percorso di corrispondenza e non dal numero di route. Tuttavia, questo approccio può essere potenzialmente problematico in alcuni casi, quando l'app ha un numero elevato di route (nelle migliaia) e c'è una quantità elevata di prefissi variabili nelle route. Ad esempio, se le route hanno parametri nei segmenti iniziali della route, ad {parameter}/some/literalesempio .

È improbabile che un'app si verifichi in una situazione in cui si tratta di un problema a meno che:

  • L'app usa questo modello per un numero elevato di route.
  • Nell'app è presente un numero elevato di route.

Come determinare se un'app è in esecuzione nel problema della tabella di route di grandi dimensioni

  • Esistono due sintomi da cercare:
    • L'app è lenta per l'avvio nella prima richiesta.
      • Si noti che questa operazione è obbligatoria ma non sufficiente. Ci sono molti altri problemi non di route che possono causare un rallentamento dell'avvio dell'app. Verificare la condizione seguente per determinare con precisione che l'app si trova in questa situazione.
    • L'app usa molta memoria durante l'avvio e un dump della memoria mostra un numero elevato di Microsoft.AspNetCore.Routing.Matching.DfaNode istanze.

Come risolvere questo problema

Esistono diverse tecniche e ottimizzazioni che possono essere applicate alle route che migliorano in gran parte questo scenario:

  • Applicare vincoli di route ai parametri, ad esempio {parameter:int}, , {parameter:guid}{parameter:regex(\\d+)}e così via, laddove possibile.
    • Ciò consente all'algoritmo di routing di ottimizzare internamente le strutture usate per la corrispondenza e ridurre drasticamente la memoria usata.
    • Nella stragrande maggioranza dei casi questo sarà sufficiente per tornare a un comportamento accettabile.
  • Modificare le route per spostare i parametri in segmenti successivi nel modello.
    • In questo modo si riduce il numero di possibili "percorsi" in modo che corrispondano a un endpoint in base a un percorso.
  • Usare una route dinamica ed eseguire il mapping a un controller/pagina in modo dinamico.
    • A tale scopo, è possibile usare MapDynamicControllerRoute e MapDynamicPageRoute.

Middleware a corto circuito dopo il routing

Quando il routing corrisponde a un endpoint, in genere consente l'esecuzione rest della pipeline middleware prima di richiamare la logica dell'endpoint. I servizi possono ridurre l'utilizzo delle risorse filtrando le richieste note nelle prime fasi della pipeline. Usare il metodo di estensione per causare il ShortCircuit routing per richiamare immediatamente la logica dell'endpoint e quindi terminare la richiesta. Ad esempio, una determinata route potrebbe non dover passare attraverso l'autenticazione o il middleware CORS. Nell'esempio seguente vengono richieste di corto circuito che corrispondono alla /short-circuit route:

app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();

Facoltativamente, il ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>) metodo può accettare un codice di stato.

Usare il MapShortCircuit metodo per configurare il corto circuito per più route contemporaneamente, passando a essa una matrice params di prefissi URL. Ad esempio, browser e bot spesso probe server per percorsi noti come robots.txt e favicon.ico. Se l'app non dispone di tali file, una riga di codice può configurare entrambe le route:

app.MapShortCircuit(404, "robots.txt", "favicon.ico");

MapShortCircuit restituisce IEndpointConventionBuilder in modo che sia possibile aggiungere vincoli di route aggiuntivi come il filtro host.

I ShortCircuit metodi e MapShortCircuit non influiscono sul middleware posizionato prima UseRoutingdi . Se si prova a usare questi metodi con endpoint con o [Authorize] metadati, [RequireCors] le richieste avranno esito negativo con .InvalidOperationException Questi metadati vengono applicati da [Authorize] attributi o [EnableCors] da RequireCors metodi o RequireAuthorization .

Per visualizzare l'effetto del middleware a corto circuito, impostare la categoria di registrazione "Microsoft" su "Information" in appsettings.Development.json:

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

Eseguire il codice seguente:

var app = WebApplication.Create();

app.UseHttpLogging();

app.MapGet("/", () => "No short-circuiting!");
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
app.MapShortCircuit(404, "robots.txt", "favicon.ico");

app.Run();

L'esempio seguente proviene dai log della console generati eseguendo l'endpoint / . Include l'output del middleware di registrazione:

info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      StatusCode: 200
      Content-Type: text/plain; charset=utf-8
      Date: Wed, 03 May 2023 21:05:59 GMT
      Server: Kestrel
      Alt-Svc: h3=":5182"; ma=86400
      Transfer-Encoding: chunked

Nell'esempio seguente viene eseguita l'esecuzione dell'endpoint /short-circuit . Non ha nulla dal middleware di registrazione perché il middleware è stato corto circuito:

info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[4]
      The endpoint 'HTTP: GET /short-circuit' is being executed without running additional middleware.
info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[5]
      The endpoint 'HTTP: GET /short-circuit' has been executed without running additional middleware.

Linee guida per gli autori di librerie

Questa sezione contiene indicazioni per gli autori di librerie che si basano sul routing. Questi dettagli sono progettati per garantire che gli sviluppatori di app abbiano un'esperienza ottimale usando librerie e framework che estendono il routing.

Definire gli endpoint

Per creare un framework che usa il routing per la corrispondenza degli URL, iniziare definendo un'esperienza utente basata su UseEndpoints.

Do build on top of IEndpointRouteBuilder. In questo modo gli utenti possono comporre il framework con altre funzionalità di ASP.NET Core senza confusione. Ogni modello ASP.NET Core include il routing. Si supponga che il routing sia presente e familiare per gli utenti.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

DO restituisce un tipo di cemento sigillato da una chiamata a MapMyFramework(...) che implementa IEndpointConventionBuilder. La maggior parte dei metodi del framework Map... segue questo modello. Interfaccia IEndpointConventionBuilder :

  • Consente la composizione dei metadati.
  • È destinato a un'ampia gamma di metodi di estensione.

La dichiarazione del proprio tipo consente di aggiungere funzionalità specifiche del framework al generatore. È possibile eseguire il wrapping di un generatore dichiarato dal framework e inoltrare le chiamate.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

PRENDERE IN CONSIDERAZIONE la scrittura di un proprio EndpointDataSourceoggetto . EndpointDataSource è la primitiva di basso livello per dichiarare e aggiornare una raccolta di endpoint. EndpointDataSource è un'API potente usata dai controller e Razor dalle pagine. Per altre informazioni, vedere Routing di endpoint dinamici.

I test di routing hanno un esempio di base di un'origine dati non aggiornata.

PRENDERE IN CONSIDERAZIONE l'implementazione GetGroupedEndpointsdi . In questo modo si ottiene il controllo completo sulle convenzioni di gruppo in esecuzione e sui metadati finali sugli endpoint raggruppati. In questo modo, ad esempio, le implementazioni personalizzate EndpointDataSource possono eseguire filtri endpoint aggiunti ai gruppi.

NON tentare di registrare un oggetto EndpointDataSource per impostazione predefinita. Richiedere agli utenti di registrare il framework in UseEndpoints. La filosofia del routing è che nulla è incluso per impostazione predefinita e che UseEndpoints è la posizione in cui registrare gli endpoint.

Creazione di middleware integrato nel routing

CONSIDERARE la definizione dei tipi di metadati come interfaccia.

DO consente di usare i tipi di metadati come attributo per classi e metodi.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Framework come controller e Razor pagine supportano l'applicazione di attributi di metadati a tipi e metodi. Se dichiari i tipi di metadati:

  • Renderli accessibili come attributi.
  • La maggior parte degli utenti ha familiarità con l'applicazione degli attributi.

La dichiarazione di un tipo di metadati come interfaccia aggiunge un altro livello di flessibilità:

  • Le interfacce sono componibili.
  • Gli sviluppatori possono dichiarare i propri tipi che combinano più criteri.

FARE in modo che sia possibile eseguire l'override dei metadati, come illustrato nell'esempio seguente:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Il modo migliore per seguire queste linee guida consiste nell'evitare di definire i metadati del marcatore:

  • Non cercare solo la presenza di un tipo di metadati.
  • Definire una proprietà sui metadati e controllare la proprietà .

La raccolta di metadati viene ordinata e supporta l'override in base alla priorità. Nel caso dei controller, i metadati nel metodo di azione sono più specifici.

FARE in modo che il middleware sia utile con e senza routing:

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

Come esempio di questa linea guida, prendere in considerazione il UseAuthorization middleware. Il middleware di autorizzazione consente di passare un criterio di fallback. I criteri di fallback, se specificati, si applicano a entrambi:

  • Endpoint senza criteri specificati.
  • Richieste che non corrispondono a un endpoint.

In questo modo il middleware di autorizzazione risulta utile al di fuori del contesto del routing. Il middleware di autorizzazione può essere usato per la programmazione middleware tradizionale.

Eseguire il debug della diagnostica

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

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

Risorse aggiuntive

Il routing è responsabile della corrispondenza delle richieste HTTP in ingresso e dell'invio di tali richieste agli endpoint eseguibili dell'app. Gli endpoint sono le unità di misura dell'app del codice di gestione delle richieste eseguibili. Gli endpoint vengono definiti nell'app e configurati all'avvio dell'app. Il processo di corrispondenza dell'endpoint può estrarre i valori dall'URL della richiesta e fornire tali valori per l'elaborazione della richiesta. Usando le informazioni sull'endpoint dall'app, il routing è anche in grado di generare URL che eseguono il mapping agli endpoint.

Le app possono configurare il routing usando:

Questo articolo illustra i dettagli di basso livello del routing ASP.NET Core. Per informazioni sulla configurazione del routing:

Nozioni fondamentali sul routing

Il codice seguente illustra un esempio di routing di base:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

L'esempio precedente include un singolo endpoint usando il MapGet metodo :

  • Quando viene inviata una richiesta HTTP GET all'URL /radice :
    • Il delegato della richiesta viene eseguito.
    • Hello World! viene scritto nella risposta HTTP.
  • Se il metodo di richiesta non GET è o l'URL radice non /è , non viene restituita alcuna corrispondenza di route e viene restituito un HTTP 404.

Il routing usa una coppia di middleware, registrati da UseRouting e UseEndpoints:

  • UseRouting aggiunge la corrispondenza della route alla pipeline middleware. Questo middleware esamina il set di endpoint definiti nell'app e seleziona la corrispondenza migliore in base alla richiesta.
  • UseEndpoints aggiunge l'esecuzione dell'endpoint alla pipeline middleware. Esegue il delegato associato all'endpoint selezionato.

Le app in genere non devono chiamare UseRouting o UseEndpoints. WebApplicationBuilder configura una pipeline middleware che esegue il wrapping del middleware aggiunto in Program.cs con UseRouting e UseEndpoints. Tuttavia, le app possono modificare l'ordine in cui UseRouting ed eseguire UseEndpoints chiamando questi metodi in modo esplicito. Ad esempio, il codice seguente effettua una chiamata esplicita a UseRouting:

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

Nel codice precedente:

  • La chiamata a app.Use registra un middleware personalizzato eseguito all'inizio della pipeline.
  • La chiamata a UseRouting configura il middleware corrispondente alla route da eseguire dopo il middleware personalizzato.
  • L'endpoint registrato con MapGet viene eseguito alla fine della pipeline.

Se l'esempio precedente non includeva una chiamata a UseRouting, il middleware personalizzato verrebbe eseguito dopo il middleware corrispondente alla route.

Endpoint

Il MapGet metodo viene usato per definire un endpoint. Un endpoint è un elemento che può essere:

  • Selezionato, associando l'URL e il metodo HTTP.
  • Eseguito eseguendo il delegato .

Gli endpoint che possono essere confrontati ed eseguiti dall'app vengono configurati in UseEndpoints. Ad esempio, MapGet, MapPoste metodi simili connettono i delegati di richiesta al sistema di routing. È possibile usare metodi aggiuntivi per connettere ASP.NET funzionalità del framework Core al sistema di routing:

L'esempio seguente mostra il routing con un modello di route più sofisticato:

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

La stringa /hello/{name:alpha} è un modello di route. Viene usato un modello di route per configurare la corrispondenza dell'endpoint. In questo caso, il modello corrisponde a:

  • UN URL come /hello/Docs
  • Qualsiasi percorso URL che inizia con /hello/ seguito da una sequenza di caratteri alfabetici. :alpha applica un vincolo di route che corrisponde solo ai caratteri alfabetici. I vincoli di route vengono illustrati più avanti in questo articolo.

Secondo segmento del percorso URL, {name:alpha}:

L'esempio seguente mostra il routing con controlli di integrità e autorizzazione:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

L'esempio precedente illustra come:

  • Il middleware di autorizzazione può essere usato con il routing.
  • Gli endpoint possono essere usati per configurare il comportamento di autorizzazione.

La MapHealthChecks chiamata aggiunge un endpoint di controllo integrità. Il concatenamento RequireAuthorization a questa chiamata collega un criterio di autorizzazione all'endpoint.

La chiamata UseAuthentication e UseAuthorization aggiunge il middleware di autenticazione e autorizzazione. Questi middleware vengono posizionati tra UseRouting e UseEndpoints in modo che possano:

  • Vedere quale endpoint è stato selezionato da UseRouting.
  • Applicare un criterio di autorizzazione prima UseEndpoints dell'invio all'endpoint.

Metadati dell'endpoint

Nell'esempio precedente sono presenti due endpoint, ma solo l'endpoint di controllo integrità ha un criterio di autorizzazione associato. Se la richiesta corrisponde all'endpoint del controllo integrità, /healthzviene eseguito un controllo di autorizzazione. Ciò dimostra che gli endpoint possono avere dati aggiuntivi collegati. Questi dati aggiuntivi sono denominati metadati dell'endpoint:

  • I metadati possono essere elaborati tramite middleware compatibile con il routing.
  • I metadati possono essere di qualsiasi tipo .NET.

Concetti relativi al routing

Il sistema di routing si basa sulla pipeline middleware aggiungendo il concetto avanzato di endpoint . Gli endpoint rappresentano unità di funzionalità dell'app distinte l'una dall'altra in termini di routing, autorizzazione e qualsiasi numero di sistemi ASP.NET Core.

definizione dell'endpoint principale ASP.NET

Un endpoint ASP.NET Core è:

Il codice seguente illustra come recuperare ed esaminare l'endpoint corrispondente alla richiesta corrente:

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

L'endpoint, se selezionato, può essere recuperato da HttpContext. È possibile controllarne le proprietà. Gli oggetti endpoint non sono modificabili e non possono essere modificati dopo la creazione. Il tipo di endpoint più comune è .RouteEndpoint RouteEndpoint include informazioni che consentono di selezionarla dal sistema di routing.

Nel codice precedente, app. Usa configura un middleware inline.

Il codice seguente mostra che, a seconda della posizione in cui app.Use viene chiamato nella pipeline, potrebbe non esserci un endpoint:

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

Nell'esempio precedente vengono aggiunte Console.WriteLine istruzioni che indicano se è stato selezionato o meno un endpoint. Per maggiore chiarezza, l'esempio assegna un nome visualizzato all'endpoint fornito / .

L'esempio precedente include anche chiamate a UseRouting e UseEndpoints per controllare esattamente quando questi middleware vengono eseguiti all'interno della pipeline.

Esecuzione di questo codice con un URL di / visualizzazione:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

L'esecuzione di questo codice con qualsiasi altro URL viene visualizzata:

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Questo output dimostra che:

  • L'endpoint è sempre Null prima UseRouting di essere chiamato.
  • Se viene trovata una corrispondenza, l'endpoint non è Null tra UseRouting e UseEndpoints.
  • Il UseEndpoints middleware è terminale quando viene trovata una corrispondenza. Il middleware del terminale viene definito più avanti in questo articolo.
  • Middleware dopo UseEndpoints l'esecuzione solo quando non viene trovata alcuna corrispondenza.

Il UseRouting middleware usa il SetEndpoint metodo per collegare l'endpoint al contesto corrente. È possibile sostituire il middleware con la UseRouting logica personalizzata e ottenere comunque i vantaggi dell'uso degli endpoint. Gli endpoint sono primitivi di basso livello come middleware e non sono associati all'implementazione del routing. La maggior parte delle app non deve sostituire UseRouting con la logica personalizzata.

Il UseEndpoints middleware è progettato per essere usato in parallelo con il UseRouting middleware. La logica di base per eseguire un endpoint non è complicata. Usare GetEndpoint per recuperare l'endpoint e quindi richiamarne la RequestDelegate proprietà.

Il codice seguente illustra come il middleware può influenzare o reagire al routing:

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

L'esempio precedente illustra due concetti importanti:

  • Il middleware può essere eseguito prima UseRouting di modificare i dati su cui opera il routing.
  • Il middleware può essere eseguito tra UseRouting e UseEndpoints per elaborare i risultati del routing prima dell'esecuzione dell'endpoint.
    • Middleware eseguito tra UseRouting e UseEndpoints:
      • In genere controlla i metadati per comprendere gli endpoint.
      • Spesso prende decisioni di sicurezza, come fatto da UseAuthorization e UseCors.
    • La combinazione di middleware e metadati consente di configurare i criteri per endpoint.

Il codice precedente mostra un esempio di middleware personalizzato che supporta i criteri per endpoint. Il middleware scrive un log di controllo dell'accesso ai dati sensibili nella console. Il middleware può essere configurato per controllare un endpoint con i RequiresAuditAttribute metadati. Questo esempio illustra un modello di consenso esplicito in cui vengono controllati solo gli endpoint contrassegnati come sensibili. È possibile definire questa logica inversa, controllando tutto ciò che non è contrassegnato come sicuro, ad esempio. Il sistema di metadati dell'endpoint è flessibile. Questa logica può essere progettata in qualsiasi modo adatto al caso d'uso.

Il codice di esempio precedente è progettato per illustrare i concetti di base degli endpoint. L'esempio non è destinato all'uso in produzione. Una versione più completa di un middleware del log di controllo è la seguente:

  • Accedere a un file o a un database.
  • Includere dettagli, ad esempio l'utente, l'indirizzo IP, il nome dell'endpoint sensibile e altro ancora.

I metadati dei criteri RequiresAuditAttribute di controllo vengono definiti come un oggetto per un Attribute uso più semplice con framework basati su classi, ad esempio controller e SignalR. Quando si usa la route al codice:

  • I metadati vengono associati a un'API del generatore.
  • I framework basati su classi includono tutti gli attributi nel metodo e nella classe corrispondenti durante la creazione di endpoint.

Le procedure consigliate per i tipi di metadati sono di definirle come interfacce o attributi. Le interfacce e gli attributi consentono il riutilizzo del codice. Il sistema di metadati è flessibile e non impone alcuna limitazione.

Confrontare il middleware del terminale con il routing

L'esempio seguente illustra sia il middleware del terminale che il routing:

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Lo stile del middleware visualizzato con Approach 1: è il middleware del terminale. Si chiama middleware del terminale perché esegue un'operazione corrispondente:

  • L'operazione di corrispondenza nell'esempio precedente è Path == "/" relativa al middleware e Path == "/Routing" al routing.
  • Quando una corrispondenza ha esito positivo, esegue alcune funzionalità e restituisce, anziché richiamare il next middleware.

Si tratta di un middleware terminale perché termina la ricerca, esegue alcune funzionalità e quindi restituisce.

L'elenco seguente confronta il middleware del terminale con il routing:

  • Entrambi gli approcci consentono di terminare la pipeline di elaborazione:
    • Il middleware termina la pipeline restituendo anziché richiamando next.
    • Gli endpoint sono sempre terminale.
  • Il middleware del terminale consente di posizionare il middleware in una posizione arbitraria nella pipeline:
    • Gli endpoint sono eseguiti nella posizione di UseEndpoints.
  • Il middleware del terminale consente al codice arbitrario di determinare quando il middleware corrisponde:
    • Il codice personalizzato di corrispondenza della route può essere dettagliato e difficile da scrivere correttamente.
    • Il routing offre soluzioni semplici per le app tipiche. La maggior parte delle app non richiede codice di corrispondenza di route personalizzato.
  • Interfaccia degli endpoint con middleware, UseAuthorization ad esempio e UseCors.
    • L'uso di un middleware del terminale con UseAuthorization o UseCors richiede l'interfaccia manuale con il sistema di autorizzazione.

Un endpoint definisce entrambi:

  • Delegato per elaborare le richieste.
  • Raccolta di metadati arbitrari. I metadati vengono usati per implementare problematiche trasversali in base ai criteri e alla configurazione collegati a ogni endpoint.

Il middleware del terminale può essere uno strumento efficace, ma può richiedere:

  • Quantità significativa di codifica e test.
  • Integrazione manuale con altri sistemi per ottenere il livello di flessibilità desiderato.

Prendere in considerazione l'integrazione con il routing prima di scrivere un middleware del terminale.

Il middleware del terminale esistente che si integra con Map o MapWhen in genere può essere trasformato in un endpoint compatibile con il routing. MapHealthChecks illustra il modello per router-ware:

Il codice seguente illustra l'uso di MapHealthChecks:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

L'esempio precedente mostra perché la restituzione dell'oggetto builder è importante. La restituzione dell'oggetto builder consente allo sviluppatore di app di configurare criteri come l'autorizzazione per l'endpoint. In questo esempio, il middleware dei controlli di integrità non ha alcuna integrazione diretta con il sistema di autorizzazione.

Il sistema di metadati è stato creato in risposta ai problemi riscontrati dagli autori dell'estendibilità usando il middleware del terminale. È problematico per ogni middleware implementare la propria integrazione con il sistema di autorizzazione.

Corrispondenza URL

  • Processo in base al quale il routing corrisponde a una richiesta in ingresso a un endpoint.
  • Si basa sui dati nel percorso e nelle intestazioni URL.
  • Può essere esteso per prendere in considerazione tutti i dati nella richiesta.

Quando viene eseguito un middleware di routing, imposta un valore Endpoint e indirizza i valori a una funzionalità di richiesta nella HttpContext richiesta corrente:

  • La chiamata a HttpContext.GetEndpoint ottiene l'endpoint.
  • HttpRequest.RouteValues ottiene la raccolta dei valori di route.

Il middleware viene eseguito dopo il middleware di routing può esaminare l'endpoint ed eseguire azioni. Ad esempio, un middleware di autorizzazione può interrogare la raccolta di metadati dell'endpoint per un criterio di autorizzazione. Dopo l'esecuzione di tutto il middleware nella pipeline di elaborazione della richiesta, viene richiamato il delegato dell'endpoint selezionato.

Il sistema di routing nell'endpoint di routing è responsabile di tutte le decisioni relative all'invio. Poiché il middleware applica i criteri in base all'endpoint selezionato, è importante:

  • Qualsiasi decisione che può influire sull'invio o sull'applicazione dei criteri di sicurezza viene effettuata all'interno del sistema di routing.

Avviso

Per garantire la compatibilità con le versioni precedenti, quando viene eseguito un delegato dell'endpoint Controller o Razor Pages, le proprietà di RouteContext.RouteData vengono impostate sui valori appropriati in base all'elaborazione della richiesta eseguita finora.

Il RouteContext tipo verrà contrassegnato come obsoleto in una versione futura:

  • Eseguire la migrazione RouteData.Values a HttpRequest.RouteValues.
  • Eseguire la migrazione RouteData.DataTokens per recuperare IDataTokensMetadata dai metadati dell'endpoint.

La corrispondenza degli URL opera in un set configurabile di fasi. In ogni fase, l'output è un set di corrispondenze. Il set di corrispondenze può essere limitato ulteriormente dalla fase successiva. L'implementazione del routing non garantisce un ordine di elaborazione per gli endpoint corrispondenti. Tutte le possibili corrispondenze vengono elaborate contemporaneamente. Le fasi di corrispondenza dell'URL si verificano nell'ordine seguente. ASP.NET Core:

  1. Elabora il percorso URL rispetto al set di endpoint e ai relativi modelli di route, raccogliendo tutte le corrispondenze.
  2. Accetta l'elenco precedente e rimuove le corrispondenze che hanno esito negativo con vincoli di route applicati.
  3. Accetta l'elenco precedente e rimuove le corrispondenze che non superano il set di MatcherPolicy istanze.
  4. Usa per EndpointSelector prendere una decisione finale dall'elenco precedente.

L'elenco degli endpoint è prioritario in base a:

Tutti gli endpoint corrispondenti vengono elaborati in ogni fase fino a quando non viene raggiunto .EndpointSelector è EndpointSelector la fase finale. Sceglie l'endpoint con priorità più alta tra le corrispondenze come corrispondenza migliore. Se sono presenti altre corrispondenze con la stessa priorità della corrispondenza migliore, viene generata un'eccezione di corrispondenza ambigua.

La precedenza della route viene calcolata in base a un modello di route più specifico a cui viene assegnata una priorità più alta. Si considerino ad esempio i modelli /hello e /{message}:

  • Entrambi corrispondono al percorso /helloURL .
  • /hello è più specifico e quindi priorità più alta.

In generale, la precedenza di route fa un buon lavoro per scegliere la corrispondenza migliore per i tipi di schemi URL usati in pratica. Usare Order solo quando necessario per evitare ambiguità.

A causa dei tipi di estendibilità forniti dal routing, non è possibile che il sistema di routing calcoli in anticipo le route ambigue. Si consideri un esempio, ad esempio i modelli /{message:alpha} di route e /{message:int}:

  • Il alpha vincolo corrisponde solo ai caratteri alfabetici.
  • Il int vincolo corrisponde solo ai numeri.
  • Questi modelli hanno la stessa precedenza di route, ma non esiste un singolo URL che corrispondono entrambi.
  • Se il sistema di routing ha segnalato un errore di ambiguità all'avvio, blocca questo caso d'uso valido.

Avviso

L'ordine delle operazioni all'interno UseEndpoints non influisce sul comportamento del routing, con un'eccezione. MapControllerRoute e MapAreaRoute assegnano automaticamente un valore di ordine ai relativi endpoint in base all'ordine in cui vengono richiamati. Questo simula il comportamento a lungo termine dei controller senza che il sistema di routing fornisca le stesse garanzie delle implementazioni di routing meno recenti.

Endpoint routing in ASP.NET Core:

  • Non ha il concetto di itinerari.
  • Non fornisce garanzie di ordinamento. Tutti gli endpoint vengono elaborati contemporaneamente.

Precedenza del modello di route e ordine di selezione dell'endpoint

La precedenza del modello di route è un sistema che assegna a ogni modello di route un valore in base al modo in cui è specifico. Precedenza del modello di route:

  • Evita la necessità di modificare l'ordine degli endpoint nei casi comuni.
  • Tenta di corrispondere alle aspettative di buon senso del comportamento di routing.

Si considerino ad esempio modelli /Products/List e /Products/{id}. Sarebbe ragionevole presupporre che /Products/List sia una corrispondenza migliore rispetto /Products/{id} al percorso /Products/ListURL . Ciò funziona perché il segmento letterale /List viene considerato avere una precedenza migliore rispetto al segmento di /{id}parametro .

I dettagli sul funzionamento della precedenza sono associati alla definizione dei modelli di route:

  • I modelli con più segmenti sono considerati più specifici.
  • Un segmento con testo letterale è considerato più specifico di un segmento di parametro.
  • Un segmento di parametro con un vincolo viene considerato più specifico di uno senza.
  • Un segmento complesso viene considerato specifico come segmento di parametro con un vincolo.
  • I parametri catch-all sono i meno specifici. Per informazioni importanti sulle route catch-all, vedere catch-all nella sezione Modelli di route.

Concetti relativi alla generazione di URL

Generazione url:

  • Processo in base al quale il routing può creare un percorso URL basato su un set di valori di route.
  • Consente una separazione logica tra gli endpoint e gli URL che vi accedono.

Il routing degli endpoint include l'API LinkGenerator . LinkGeneratorè un servizio singleton disponibile dall'inserimento delle dipendenze. L'API LinkGenerator può essere usata al di fuori del contesto di una richiesta in esecuzione. Mvc.IUrlHelper e scenari che si basano su IUrlHelper, ad esempio helper tag, helper HTML e risultati azione, usano l'API LinkGenerator internamente per fornire funzionalità di generazione di collegamenti.

Il generatore di collegamenti si basa sui concetti di indirizzo e di schemi di indirizzi. Lo schema di indirizzi consente di determinare gli endpoint che devono essere considerati per la generazione dei collegamenti. Ad esempio, gli scenari di nome della route e valori di route con cui molti utenti hanno familiarità con i controller e Razor le pagine vengono implementati come schema di indirizzi.

Il generatore di collegamenti può collegarsi ai controller e Razor alle pagine tramite i metodi di estensione seguenti:

Gli overload di questi metodi accettano argomenti che includono .HttpContext Questi metodi sono funzionalmente equivalenti a Url.Action e Url.Page, ma offrono flessibilità e opzioni aggiuntive.

I GetPath* metodi sono più simili a Url.Action e Url.Page, in quanto generano un URI contenente un percorso assoluto. I metodi GetUri* generano sempre un URI assoluto contenente uno schema e un host. I metodi che accettano HttpContext generano un URI nel contesto della richiesta in esecuzione. I valori di route di ambiente , il percorso di base URL, lo schema e l'host della richiesta in esecuzione vengono usati a meno che non venga sottoposto a override.

LinkGenerator viene chiamato con un indirizzo. La generazione di un URI viene eseguita in due passaggi:

  1. Un indirizzo viene associato a un elenco di endpoint che corrispondono all'indirizzo.
  2. RoutePattern di ogni endpoint viene valutato fino a quando non viene individuato un formato di route che corrisponde ai valori specificati. L'output risultante viene unito alle altre parti dell'URI specificate nel generatore di collegamenti e restituito.

I metodi forniti da LinkGenerator supportano le funzionalità di generazione di collegamenti standard per tutti i tipi di indirizzi. Il modo più pratico per usare il generatore di collegamenti è tramite metodi di estensione che eseguono operazioni per un tipo di indirizzo specifico:

Metodo di estensione Descrizione
GetPathByAddress Genera un URI con un percorso assoluto in base ai valori specificati.
GetUriByAddress Genera un URI assoluto in base ai valori specificati.

Avviso

Prestare attenzione alle implicazioni seguenti della chiamata ai metodi LinkGenerator:

  • Usare i metodi di estensione GetUri* con cautela in una configurazione di app che non convalida l'intestazione Host delle richieste in ingresso. Se l'intestazione delle richieste in ingresso non viene convalidata, l'input Host di richiesta non attendibile può essere inviato al client negli URI di una visualizzazione o di una pagina. È consigliabile che in tutte le app di produzione il server sia configurato per la convalida dell'intestazione Host rispetto ai valori validi noti.

  • Usare LinkGenerator con cautela nel middleware in associazione a Map o MapWhen. Map* modifica il percorso di base della richiesta in esecuzione, che ha effetto sull'output della generazione di collegamenti. Tutte le API LinkGenerator consentono di specificare un percorso di base. Specificare un percorso di base vuoto per annullare l'effetto Map* sulla generazione del collegamento.

Esempio di middleware

Nell'esempio seguente un middleware usa l'API LinkGenerator per creare un collegamento a un metodo di azione che elenca i prodotti dell'archivio. L'uso del generatore di collegamenti inserendolo in una classe e chiamando GenerateLink è disponibile per qualsiasi classe in un'app:

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

Modelli di route

I token all'interno {} definiscono i parametri di route associati se la route corrisponde. È possibile definire più parametri di route in un segmento di route, ma i parametri di route devono essere separati da un valore letterale. Ad esempio:

{controller=Home}{action=Index}

non è una route valida, perché non esiste alcun valore letterale tra {controller} e {action}. I parametri di route devono avere un nome e possono avere attributi aggiuntivi specificati.

Il testo letterale diverso dai parametri di route, ad esempio {id}, e il separatore di percorso / devono corrispondere al testo nell'URL. La corrispondenza del testo non fa distinzione tra maiuscole e minuscole e si basa sulla rappresentazione decodificata del percorso dell'URL. Per trovare una corrispondenza con un delimitatore { di parametro di route letterale o }, eseguire l'escape del delimitatore ripetendo il carattere. Ad esempio {{ o }}.

Asterisco * o asterisco **doppio:

  • Può essere usato come prefisso per un parametro di route da associare all'URI rest .
  • Vengono chiamati parametri catch-all . Ad esempio, blog/{**slug}:
    • Trova la corrispondenza con qualsiasi URI che inizia con blog/ e ha qualsiasi valore dopo di esso.
    • Il valore seguente blog/ viene assegnato al valore di route slug .

Avviso

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

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

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

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

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

I parametri catch-all possono anche corrispondere alla stringa vuota.

Il parametro catch-all esegue l'escape dei caratteri appropriati quando viene usata la route per generare un URL, inclusi i caratteri separatori / di percorso. Ad esempio, la route foo/{*path} con i valori di route { path = "my/path" } genera foo/my%2Fpath. Si noti la barra di escape. Per eseguire il round trip dei caratteri di separatore di percorso, usare il prefisso del parametro di route **. La route foo/{**path} con { path = "my/path" } genera foo/my/path.

I modelli di URL che tentano di acquisire un nome file con un'estensione facoltativa hanno considerazioni aggiuntive. Considerare ad esempio il modello files/{filename}.{ext?}. Se esistono i valori per filename e ext, vengono popolati entrambi i valori. Se nell'URL esiste solo un valore per filename , la route corrisponde perché l'elemento finale . è facoltativo. Gli URL seguenti corrispondono a questa route:

  • /files/myFile.txt
  • /files/myFile

I parametri di route possono avere valori predefiniti, definiti specificando il valore predefinito dopo il nome del parametro, separato da un segno di uguale (=). Ad esempio, {controller=Home} definisce Home come valore predefinito per controller. Il valore predefinito viene usato se nell'URL non è presente alcun valore per il parametro. I parametri di route vengono resi facoltativi aggiungendo un punto interrogativo (?) alla fine del nome del parametro. Ad esempio: id?. La differenza tra i valori facoltativi e i parametri di route predefiniti è:

  • Un parametro di route con un valore predefinito produce sempre un valore.
  • Un parametro facoltativo ha un valore solo quando un valore viene fornito dall'URL della richiesta.

I parametri di route possono presentare dei vincoli che devono corrispondere al valore di route associato dall'URL. L'aggiunta : del nome del vincolo e dopo il nome del parametro di route specifica un vincolo inline su un parametro di route. Se il vincolo richiede argomenti, vengono racchiusi tra parentesi (...) dopo il nome del vincolo. È possibile specificare più vincoli inline aggiungendo un altro : nome di vincolo e .

Il nome del vincolo e gli argomenti vengono passati al servizio IInlineConstraintResolver per creare un'istanza di IRouteConstraint da usare nell'elaborazione dell'URL. Il modello di route blog/{article:minlength(10)} specifica ad esempio un vincolo minlength con l'argomento 10. Per altre informazioni sui vincoli di route e un elenco dei vincoli forniti dal framework, vedere la sezione Vincoli di route.

I parametri di route possono avere anche trasformatori di parametro. I trasformatori di parametro trasformano il valore di un parametro durante la generazione di collegamenti e azioni e pagine corrispondenti agli URL. Analogamente ai vincoli, i trasformatori di parametro possono essere aggiunti inline a un parametro di route aggiungendo un : nome di trasformatore e dopo il nome del parametro di route. Ad esempio, il modello di route blog/{article:slugify} specifica un trasformatore slugify. Per altre informazioni sui trasformatori di parametri, vedere la sezione Trasformatori di parametro.

La tabella seguente illustra modelli di route di esempio e il relativo comportamento:

Modello di route URI corrispondente di esempio L'URI della richiesta
hello /hello Verifica la corrispondenza solo del singolo percorso /hello.
{Page=Home} / Verifica la corrispondenza e imposta Page su Home.
{Page=Home} /Contact Verifica la corrispondenza e imposta Page su Contact.
{controller}/{action}/{id?} /Products/List Esegue il mapping al controller Products e all'azione List.
{controller}/{action}/{id?} /Products/Details/123 Esegue il Products mapping al controller e Details all'azione conid impostato su 123.
{controller=Home}/{action=Index}/{id?} / Esegue il mapping al controller e Index al Home metodo . id viene ignorato.
{controller=Home}/{action=Index}/{id?} /Products Esegue il mapping al controller e Index al Products metodo . id viene ignorato.

L'uso di un modello è in genere l'approccio più semplice al routing. I vincoli e le impostazioni predefinite possono essere specificati anche all'esterno del modello di route.

Segmenti complessi

I segmenti complessi vengono elaborati associando delimitatori letterali da destra a sinistra in modo non greedy . Ad esempio, [Route("/a{b}c{d}")] è un segmento complesso. I segmenti complessi funzionano in modo particolare che devono essere compresi per usarli correttamente. L'esempio in questa sezione illustra perché i segmenti complessi funzionano correttamente solo quando il testo del delimitatore non viene visualizzato all'interno dei valori dei parametri. L'uso di un'espressione regolare e quindi l'estrazione manuale dei valori è necessaria per casi più complessi.

Avviso

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

Si tratta di un riepilogo dei passaggi che il routing esegue con il modello /a{b}c{d} e il percorso /abcdURL . Viene | usato per visualizzare il funzionamento dell'algoritmo:

  • Il primo valore letterale, da destra a sinistra, è c. Quindi /abcd viene eseguita la ricerca da destra e trova /ab|c|d.
  • Tutti gli elementi a destra (d) vengono ora confrontati con il parametro {d}di route .
  • Il valore letterale successivo, da destra a sinistra, è a. Quindi /ab|c|d viene eseguita una ricerca a partire dal punto in cui è stato interrotto, quindi a viene trovato /|a|b|c|d.
  • Il valore a destra (b) viene ora confrontato con il parametro {b}di route .
  • Nessun testo rimanente e nessun modello di route rimanente, quindi si tratta di una corrispondenza.

Di seguito è riportato un esempio di caso negativo usando lo stesso modello /a{b}c{d} e il percorso /aabcdURL . Viene | usato per visualizzare il funzionamento dell'algoritmo. Questo caso non è una corrispondenza, spiegata dallo stesso algoritmo:

  • Il primo valore letterale, da destra a sinistra, è c. Quindi /aabcd viene eseguita la ricerca da destra e trova /aab|c|d.
  • Tutti gli elementi a destra (d) vengono ora confrontati con il parametro {d}di route .
  • Il valore letterale successivo, da destra a sinistra, è a. Quindi /aab|c|d viene eseguita una ricerca a partire dal punto in cui è stato interrotto, quindi a viene trovato /a|a|b|c|d.
  • Il valore a destra (b) viene ora confrontato con il parametro {b}di route .
  • A questo punto è presente testo arimanente, ma l'algoritmo ha esaurito il modello di route da analizzare, quindi non si tratta di una corrispondenza.

Poiché l'algoritmo corrispondente non è greedy:

  • Corrisponde alla quantità minima di testo possibile in ogni passaggio.
  • Ogni caso in cui il valore del delimitatore venga visualizzato all'interno dei valori dei parametri non corrisponde.

Le espressioni regolari offrono un maggiore controllo sul comportamento di corrispondenza.

La corrispondenza Greedy, nota anche come corrispondenza differita, corrisponde alla stringa più grande possibile. Non greedy corrisponde alla stringa più piccola possibile.

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;

Vincoli della route

I vincoli di route vengono eseguiti quando si verifica una corrispondenza nell'URL in ingresso e il percorso URL viene suddiviso in valori di route in formato token. I vincoli di route in genere controllano il valore di route associato tramite il modello di route e prendere una decisione vera o falsa circa se il valore è accettabile. Alcuni vincoli di route usano i dati all'esterno del valore di route per stabilire se la richiesta può essere instradata. Ad esempio, HttpMethodRouteConstraint può accettare o rifiutare una richiesta in base al relativo verbo HTTP. I vincoli vengono usati nelle richieste del routing e nella generazione di collegamenti.

Avviso

Non usare i vincoli per la convalida dell'input. Se i vincoli vengono usati per la convalida dell'input, l'input non valido restituisce una 404 risposta non trovata. L'input non valido deve produrre una 400 richiesta non valida con un messaggio di errore appropriato. I vincoli di route vengono usati per evitare ambiguità tra route simili, non per convalidare gli input per una route specifica.

La tabella seguente illustra i vincoli di route di esempio e il relativo comportamento previsto:

vincolo Esempio Esempi di corrispondenza Note
int {id:int} 123456789, -123456789 Corrisponde a qualsiasi numero intero
bool {active:bool} true, FALSE Corrisponde true a o false. Nessuna distinzione tra maiuscole e minuscole
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Corrisponde a un valore valido DateTime nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
decimal {price:decimal} 49.99, -1,000.01 Corrisponde a un valore valido decimal nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
double {weight:double} 1.234, -1,001.01e8 Corrisponde a un valore valido double nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
float {weight:float} 1.234, -1,001.01e8 Corrisponde a un valore valido float nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Corrisponde a un valore Guid valido
long {ticks:long} 123456789, -123456789 Corrisponde a un valore long valido
minlength(value) {username:minlength(4)} Rick La stringa deve contenere almeno 4 caratteri
maxlength(value) {filename:maxlength(8)} MyFile La stringa non deve contenere più di 8 caratteri
length(length) {filename:length(12)} somefile.txt La stringa deve contenere esattamente 12 caratteri
length(min,max) {filename:length(8,16)} somefile.txt La stringa deve contenere almeno 8 e non più di 16 caratteri
min(value) {age:min(18)} 19 Il valore intero deve essere almeno 18
max(value) {age:max(120)} 91 Il valore intero non deve essere superiore a 120
range(min,max) {age:range(18,120)} 91 Il valore intero deve essere almeno 18 ma non più di 120
alpha {name:alpha} Rick La stringa deve essere costituita da uno o più caratteri a-z alfabetici e senza distinzione tra maiuscole e minuscole.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 La stringa deve corrispondere all'espressione regolare. Vedere suggerimenti sulla definizione di un'espressione regolare.
required {name:required} Rick Usato per imporre che un valore diverso da un parametro sia presente durante la generazione dell'URL

Avviso

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

Più vincoli delimitati da due punti possono essere applicati a un singolo parametro. Ad esempio il vincolo seguente limita un parametro a un valore intero maggiore o uguale a 1:

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Avviso

I vincoli di route che verificano l'URL e vengono convertiti in un tipo CLR usano sempre le impostazioni cultura invarianti. Ad esempio, la conversione nel tipo int CLR o DateTime. Questi vincoli presuppongono che l'URL non sia localizzabile. I vincoli di route specificati dal framework non modificano i valori archiviati nei valori di route. Tutti i valori di route analizzati dall'URL vengono archiviati come stringhe. Ad esempio, il vincolo float prova a convertire il valore di route in un valore float, ma il valore convertito viene usato solo per verificare che può essere convertito in un valore float.

Espressioni regolari nei vincoli

Avviso

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

Le espressioni regolari possono essere specificate come vincoli inline usando il vincolo di regex(...) route. I metodi nella MapControllerRoute famiglia accettano anche un valore letterale oggetto di vincoli. Se viene utilizzata la maschera, i valori stringa vengono interpretati come espressioni regolari.

Il codice seguente usa un vincolo regex inline:

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

Il codice seguente usa un valore letterale oggetto per specificare un vincolo regex:

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

Il framework di ASP.NET Core aggiunge RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant al costruttore di espressioni regolari. Per una descrizione di questi membri, vedere RegexOptions.

Le espressioni regolari usano delimitatori e token simili a quelli usati dal routing e dal linguaggio C#. Per i token di espressione è necessario aggiungere caratteri di escape. Per usare l'espressione ^\d{3}-\d{2}-\d{4}$ regolare in un vincolo inline, usare una delle opzioni seguenti:

  • Sostituire i \ caratteri specificati nella stringa come \\ caratteri nel file di origine C# per eseguire l'escape del carattere di escape della \ stringa.
  • Valori letterali stringa verbatim.

Per eseguire l'escape dei caratteri {delimitatori dei parametri di routing , [}, , ]double i caratteri nell'espressione, ad esempio , [[{{}}, , . ]] La tabella seguente mostra un'espressione regolare e la relativa versione con escape:

Espressione regolare Espressione regolare preceduta da escape
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Le espressioni regolari usate nel routing spesso iniziano con il ^ carattere e corrispondono alla posizione iniziale della stringa. Le espressioni terminano spesso con il $ carattere e corrispondono alla fine della stringa. I ^ caratteri e $ assicurano che l'espressione regolare corrisponda all'intero valore del parametro di route. Senza i ^ caratteri e $ , l'espressione regolare corrisponde a qualsiasi sottostringa all'interno della stringa, spesso indesiderata. La tabella seguente fornisce esempi e spiega il motivo per cui corrispondono o non corrispondono:

Espressione String Match Commento
[a-z]{2} hello Corrispondenze di sottostringhe
[a-z]{2} 123abc456 Corrispondenze di sottostringhe
[a-z]{2} mz Corrisponde all'espressione
[a-z]{2} MZ Senza distinzione maiuscole/minuscole
^[a-z]{2}$ hello No Vedere ^ e $ sopra
^[a-z]{2}$ 123abc456 No Vedere ^ e $ sopra

Per altre informazioni sulla sintassi delle espressioni regolari, vedere Espressioni regolari di .NET Framework.

Per limitare un parametro a un set noto di valori possibili, usare un'espressione regolare. Ad esempio, {action:regex(^(list|get|create)$)} verifica la corrispondenza del valore di route action solo con list, get o create. Se viene passata nel dizionario di vincoli, la stringa ^(list|get|create)$ è equivalente. I vincoli passati nel dizionario vincoli che non corrispondono a uno dei vincoli noti vengono considerati anche come espressioni regolari. I vincoli passati all'interno di un modello che non corrispondono a uno dei vincoli noti non vengono considerati come espressioni regolari.

Vincoli di route personalizzati

È possibile creare vincoli di route personalizzati implementando l'interfaccia IRouteConstraint . L'interfaccia IRouteConstraint contiene Match, che restituisce true se il vincolo è soddisfatto e false in caso contrario.

I vincoli di route personalizzati sono raramente necessari. Prima di implementare un vincolo di route personalizzato, prendere in considerazione alternative, ad esempio l'associazione di modelli.

La cartella ASP.NET vincoli principali fornisce esempi validi di creazione di vincoli. Ad esempio, GuidRouteConstraint.

Per usare un oggetto personalizzato IRouteConstraint, il tipo di vincolo di route deve essere registrato con l'app ConstraintMap nel contenitore del servizio. ConstraintMap è un dizionario che esegue il mapping delle chiavi dei vincoli di route alle implementazioni di IRouteConstraint che convalidano tali vincoli. Un'app ConstraintMap può essere aggiornata in Program.cs come parte di una AddRouting chiamata o configurando RouteOptions direttamente con builder.Services.Configure<RouteOptions>. Ad esempio:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

Il vincolo precedente viene applicato nel codice seguente:

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

L'implementazione di NoZeroesRouteConstraint impedisce l'uso 0 in un parametro di route:

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Avviso

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

Il codice precedente:

  • Impedisce 0 nel {id} segmento della route.
  • Viene illustrato come fornire un esempio di base dell'implementazione di un vincolo personalizzato. Non deve essere usato in un'app di produzione.

Il codice seguente è un approccio migliore per impedire l'elaborazione di un oggetto id contenente un 0 oggetto :

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

Il codice precedente presenta i vantaggi seguenti rispetto all'approccio NoZeroesRouteConstraint :

  • Non richiede un vincolo personalizzato.
  • Restituisce un errore più descrittivo quando il parametro di route include 0.

Trasformatori di parametro

I trasformatori di parametro:

Ad esempio, un trasformatore di parametro slugify personalizzato nel modello di route blog\{article:slugify} con Url.Action(new { article = "MyTestArticle" }) genera blog\my-test-article.

Si consideri l'implementazione seguente IOutboundParameterTransformer:

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value is null)
        {
            return null;
        }

        return Regex.Replace(
            value.ToString()!,
                "([a-z])([A-Z])",
            "$1-$2",
            RegexOptions.CultureInvariant,
            TimeSpan.FromMilliseconds(100))
            .ToLowerInvariant();
    }
}

Per usare un trasformatore di parametro in un modello di route, configurarlo usando ConstraintMap in Program.cs:

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

Il framework ASP.NET Core usa trasformatori di parametro per trasformare l'URI in cui viene risolto un endpoint. Ad esempio, i trasformatori di parametro trasformano i valori di route usati per trovare la corrispondenza con un areaoggetto , controlleraction, e page:

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

Con il modello di route precedente, l'azione SubscriptionManagementController.GetAll viene confrontata con l'URI /subscription-management/get-all. Un trasformatore di parametro non modifica i valori della route usati per generare un collegamento. Ad esempio, Url.Action("GetAll", "SubscriptionManagement") restituisce /subscription-management/get-all.

ASP.NET Core fornisce convenzioni API per l'uso di trasformatori di parametri con route generate:

Riferimento per la generazione di URL

Questa sezione contiene un riferimento per l'algoritmo implementato dalla generazione di URL. In pratica, esempi più complessi di generazione url usano controller o Razor pagine. Per altre informazioni, vedere Routing nei controller .

Il processo di generazione url inizia con una chiamata a LinkGenerator.GetPathByAddress o un metodo simile. Il metodo viene fornito con un indirizzo, un set di valori di route e facoltativamente informazioni sulla richiesta corrente da HttpContext.

Il primo passaggio consiste nell'usare l'indirizzo per risolvere un set di endpoint candidati usando un oggetto IEndpointAddressScheme<TAddress> che corrisponde al tipo di indirizzo.

Dopo che il set di candidati viene trovato dallo schema di indirizzi, gli endpoint vengono ordinati ed elaborati in modo iterativo fino a quando non viene completata un'operazione di generazione url. La generazione di URL non verifica la presenza di ambiguità, il primo risultato restituito è il risultato finale.

Risoluzione dei problemi relativi alla generazione di URL con la registrazione

Il primo passaggio per la risoluzione dei problemi relativi alla generazione di URL consiste nell'impostare il livello di registrazione di Microsoft.AspNetCore.Routing su TRACE. LinkGenerator registra molti dettagli sull'elaborazione che può essere utile per risolvere i problemi.

Per informazioni dettagliate sulla generazione di URL, vedere Informazioni di riferimento sulla generazione di URL.

Indirizzi

Gli indirizzi sono il concetto nella generazione di URL usato per associare una chiamata al generatore di collegamenti a un set di endpoint candidati.

Gli indirizzi sono un concetto estendibile che include due implementazioni per impostazione predefinita:

  • Uso del nome dell'endpoint (string) come indirizzo:
    • Fornisce funzionalità simili al nome della route di MVC.
    • Usa il IEndpointNameMetadata tipo di metadati.
    • Risolve la stringa fornita rispetto ai metadati di tutti gli endpoint registrati.
    • Genera un'eccezione all'avvio se più endpoint usano lo stesso nome.
    • Consigliato per l'uso generico all'esterno di controller e Razor pagine.
  • Uso dei valori di route (RouteValuesAddress) come indirizzo:
    • Fornisce funzionalità simili ai controller e Razor alla generazione di URL legacy di Pages.
    • Molto complesso da estendere ed eseguire il debug.
    • Fornisce l'implementazione usata da IUrlHelper, helper tag, helper HTML, risultati azione e così via.

Il ruolo dello schema di indirizzi consiste nell'eseguire l'associazione tra l'indirizzo e gli endpoint corrispondenti in base a criteri arbitrari:

  • Lo schema dei nomi dell'endpoint esegue una ricerca di dizionario di base.
  • Lo schema dei valori di route ha un subset migliore complesso dell'algoritmo set.

Valori di ambiente e valori espliciti

Dalla richiesta corrente, il routing accede ai valori di route della richiesta HttpContext.Request.RouteValuescorrente. I valori associati alla richiesta corrente vengono definiti valori di ambiente. Ai fini della chiarezza, la documentazione fa riferimento ai valori di route passati ai metodi come valori espliciti.

Nell'esempio seguente vengono illustrati i valori di ambiente e i valori espliciti. Fornisce i valori di ambiente della richiesta corrente e dei valori espliciti:

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

Il codice precedente:

Il codice seguente fornisce solo valori espliciti e nessun valore di ambiente:

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

Il metodo precedente restituisce /Home/Subscribe/17

Il codice seguente in WidgetController restituisce /Widget/Subscribe/17:

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

Il codice seguente fornisce il controller dai valori di ambiente nella richiesta corrente e nei valori espliciti:

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

Nel codice precedente:

  • /Gadget/Edit/17 viene restituito.
  • Url ottiene l'oggetto IUrlHelper.
  • Action genera un URL con un percorso assoluto per un metodo di azione. L'URL contiene il nome e route i valori specificatiaction.

Il codice seguente fornisce i valori di ambiente della richiesta corrente e dei valori espliciti:

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

Il codice precedente imposta su url /Edit/17 quando la pagina di modifica Razor contiene la direttiva di pagina seguente:

@page "{id:int}"

Se la pagina Modifica non contiene il "{id:int}" modello di route, url è /Edit?id=17.

Il comportamento di MVC IUrlHelper aggiunge un livello di complessità oltre alle regole descritte di seguito:

  • IUrlHelper fornisce sempre i valori di route della richiesta corrente come valori di ambiente.
  • IUrlHelper.Action copia sempre i valori correnti action e controller di route come valori espliciti, a meno che non venga sottoposto a override dallo sviluppatore.
  • IUrlHelper.Page copia sempre il valore di route corrente page come valore esplicito, a meno che non venga sottoposto a override.
  • IUrlHelper.Page esegue sempre l'override del valore di route corrente handler con null come valori espliciti, a meno che non venga sottoposto a override.

Gli utenti sono spesso sorpresi dai dettagli comportamentali dei valori di ambiente, perché MVC non sembra seguire le proprie regole. Per motivi cronologici e di compatibilità, alcuni valori di route, actionad esempio , controllerpage, e handler hanno un comportamento specifico.

La funzionalità equivalente fornita da LinkGenerator.GetPathByAction e LinkGenerator.GetPathByPage duplica queste anomalie di IUrlHelper per la compatibilità.

Processo di generazione url

Dopo aver trovato il set di endpoint candidati, l'algoritmo di generazione url:

  • Elabora gli endpoint in modo iterativo.
  • Restituisce il primo risultato riuscito.

Il primo passaggio di questo processo è denominato invalidazione del valore di route. L'invalidazione del valore di route è il processo in base al quale il routing decide quali valori di route dai valori di ambiente devono essere usati e quali devono essere ignorati. Ogni valore di ambiente viene considerato e combinato con i valori espliciti o ignorato.

Il modo migliore per considerare il ruolo dei valori di ambiente è che tentano di salvare gli sviluppatori di applicazioni digitando, in alcuni casi comuni. Tradizionalmente, gli scenari in cui i valori di ambiente sono utili sono correlati a MVC:

  • Quando si esegue il collegamento a un'altra azione nello stesso controller, non è necessario specificare il nome del controller.
  • Quando si esegue il collegamento a un altro controller nella stessa area, non è necessario specificare il nome dell'area.
  • Quando si esegue il collegamento allo stesso metodo di azione, non è necessario specificare i valori di route.
  • Quando si esegue il collegamento a un'altra parte dell'app, non si vogliono trasportare valori di route che non hanno alcun significato in tale parte dell'app.

Le chiamate a LinkGenerator o IUrlHelper che restituiscono null sono in genere causate dalla mancata comprensione dell'invalidazione del valore di route. Risolvere i problemi di invalidazione del valore di route specificando in modo esplicito più valori di route per verificare se questo risolve il problema.

L'invalidazione del valore di route si basa sul presupposto che lo schema URL dell'app sia gerarchico, con una gerarchia formata da sinistra a destra. Si consideri il modello {controller}/{action}/{id?} di route del controller di base per avere un'idea intuitiva del funzionamento di questa operazione in pratica. Una modifica a un valore invalida tutti i valori di route visualizzati a destra. Ciò riflette il presupposto sulla gerarchia. Se l'app ha un valore di ambiente per ide l'operazione specifica un valore diverso per :controller

  • id non verrà riutilizzato perché {controller} si trova a sinistra di {id?}.

Alcuni esempi che illustrano questo principio:

  • Se i valori espliciti contengono un valore per id, il valore di ambiente per id viene ignorato. I valori di ambiente per controller e action possono essere usati.
  • Se i valori espliciti contengono un valore per action, qualsiasi valore di ambiente per action viene ignorato. È possibile usare i valori di ambiente per controller . Se il valore esplicito per action è diverso dal valore di ambiente per action, il id valore non verrà usato. Se il valore esplicito per action è uguale al valore di ambiente per action, è possibile usare il id valore .
  • Se i valori espliciti contengono un valore per controller, qualsiasi valore di ambiente per controller viene ignorato. Se il valore esplicito per controller è diverso dal valore di ambiente per controller, i action valori e id non verranno usati. Se il valore esplicito per controller è uguale al valore di ambiente per controller, è possibile usare i action valori e id .

Questo processo è ulteriormente complicato dall'esistenza di route di attributi e route convenzionali dedicate. Route convenzionali del controller, ad {controller}/{action}/{id?} esempio specificare una gerarchia usando i parametri di route. Per route convenzionali dedicate e route di attributi ai controller e Razor alle pagine:

  • Esiste una gerarchia di valori di route.
  • Non vengono visualizzati nel modello.

Per questi casi, la generazione di URL definisce il concetto di valori necessari. Gli endpoint creati dai controller e Razor pagine hanno valori obbligatori specificati che consentono il funzionamento dell'invalidazione del valore di route.

Algoritmo di invalidazione del valore della route in dettaglio:

  • I nomi dei valori obbligatori vengono combinati con i parametri di route, quindi elaborati da sinistra a destra.
  • Per ogni parametro vengono confrontati il valore di ambiente e il valore esplicito:
    • Se il valore di ambiente e il valore esplicito sono uguali, il processo continua.
    • Se il valore di ambiente è presente e il valore esplicito non è , il valore di ambiente viene usato durante la generazione dell'URL.
    • Se il valore di ambiente non è presente e il valore esplicito è , rifiutare il valore di ambiente e tutti i valori di ambiente successivi.
    • Se sono presenti il valore di ambiente e il valore esplicito e i due valori sono diversi, rifiutare il valore di ambiente e tutti i valori di ambiente successivi.

A questo punto, l'operazione di generazione url è pronta per valutare i vincoli di route. Il set di valori accettati viene combinato con i valori predefiniti del parametro, forniti ai vincoli. Se tutti i vincoli vengono passati, l'operazione continua.

Successivamente, i valori accettati possono essere usati per espandere il modello di route. Il modello di route viene elaborato:

  • Da sinistra a destra.
  • Ogni parametro ha il valore accettato sostituito.
  • Con i casi speciali seguenti:
    • Se i valori accettati mancano un valore e il parametro ha un valore predefinito, viene usato il valore predefinito.
    • Se i valori accettati mancano un valore e il parametro è facoltativo, l'elaborazione continua.
    • Se un parametro di route a destra di un parametro facoltativo mancante ha un valore, l'operazione ha esito negativo.
    • I parametri con valori predefiniti contigui e i parametri facoltativi vengono compressi laddove possibile.

I valori specificati in modo esplicito che non corrispondono a un segmento della route vengono aggiunti alla stringa di query. La tabella seguente illustra il risultato ottenuto quando si usa il modello di route {controller}/{action}/{id?}.

Valori di ambiente Valori espliciti Risultato
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", colore = "Rosso" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

Ordine dei parametri di route facoltativo

I parametri di route facoltativi devono venire dopo tutti i parametri di route necessari. Nel codice seguente i id parametri e name devono provenire dopo il color parametro :

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[Route("api/[controller]")]
public class MyController : ControllerBase
{
    // GET /api/my/red/2/joe
    // GET /api/my/red/2
    // GET /api/my
    [HttpGet("{color}/{id:int?}/{name?}")]
    public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
    {
        return Ok($"{color} {id} {name ?? ""}");
    }
}

Problemi con l'invalidazione del valore di route

Il codice seguente mostra un esempio di schema di generazione url non supportato dal routing:

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

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

Nel codice precedente il parametro di route viene usato per la culture localizzazione. Il desiderio è avere il culture parametro sempre accettato come valore di ambiente. Tuttavia, il culture parametro non viene accettato come valore di ambiente a causa del funzionamento dei valori richiesti:

  • "default" Nel modello di route il culture parametro di route si trova a sinistra di controller, quindi le modifiche apportate a controller non invalidano culture.
  • "blog" Nel modello di route il culture parametro di route viene considerato a destra di controller, che viene visualizzato nei valori necessari.

Analizzare i percorsi URL con LinkParser

La LinkParser classe aggiunge il supporto per l'analisi di un percorso URL in un set di valori di route. Il ParsePathByEndpointName metodo accetta un nome di endpoint e un percorso URL e restituisce un set di valori di route estratti dal percorso URL.

Nel controller di esempio seguente, l'azione GetProduct usa un modello di route di api/Products/{id} e ha un Name valore di GetProduct:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

Nella stessa classe controller, l'azione AddRelatedProduct prevede un percorso URL, pathToRelatedProduct, che può essere fornito come parametro di stringa di query:

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

Nell'esempio precedente l'azione AddRelatedProduct estrae il valore di id route dal percorso URL. Ad esempio, con un percorso URL di /api/Products/1, il relatedProductId valore è impostato su 1. Questo approccio consente ai client dell'API di usare i percorsi URL quando si fa riferimento alle risorse, senza richiedere la conoscenza della struttura di tale URL.

Configurare i metadati dell'endpoint

I collegamenti seguenti forniscono informazioni su come configurare i metadati dell'endpoint:

Corrispondenza dell'host nelle route con RequireHost

RequireHost applica un vincolo alla route che richiede l'host specificato. Il RequireHost parametro o [Host] può essere:

  • Host: www.domain.com, corrisponde www.domain.com a qualsiasi porta.
  • Host con carattere jolly: *.domain.com, corrisponde www.domain.coma , subdomain.domain.como www.subdomain.domain.com su qualsiasi porta.
  • Porta: *:5000, corrisponde alla porta 5000 con qualsiasi host.
  • Host e porta: www.domain.com:5000 o *.domain.com:5000, corrisponde all'host e alla porta.

È possibile specificare più parametri usando RequireHost o [Host]. Il vincolo corrisponde agli host validi per uno dei parametri. Ad esempio, [Host("domain.com", "*.domain.com")] corrisponde a domain.com, www.domain.come subdomain.domain.com.

Il codice seguente usa RequireHost per richiedere l'host specificato nella route:

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

Il codice seguente usa l'attributo [Host] nel controller per richiedere uno degli host specificati:

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

Quando l'attributo [Host] viene applicato sia al controller che al metodo di azione:

  • Viene usato l'attributo sull'azione.
  • L'attributo controller viene ignorato.

Gruppi di route

Il MapGroup metodo di estensione consente di organizzare gruppi di endpoint con un prefisso comune. Riduce il codice ripetitivo e consente di personalizzare interi gruppi di endpoint con una singola chiamata a metodi come RequireAuthorization e WithMetadata che aggiungono metadati dell'endpoint.

Ad esempio, il codice seguente crea due gruppi simili di endpoint:

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

In questo scenario è possibile usare un indirizzo relativo per l'intestazione Location nel 201 Created risultato:

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

Il primo gruppo di endpoint corrisponderà solo alle richieste precedute da /public/todos e sono accessibili senza alcuna autenticazione. Il secondo gruppo di endpoint corrisponderà solo alle richieste precedute /private/todos da e richiederanno l'autenticazione.

La QueryPrivateTodos factory del filtro endpoint è una funzione locale che modifica i parametri del TodoDb gestore di route per consentire l'accesso e l'archiviazione di dati todo privati.

I gruppi di route supportano anche gruppi annidati e modelli di prefisso complessi con parametri e vincoli di route. Nell'esempio seguente e il gestore di route mappato al user gruppo possono acquisire i {org} parametri e {group} di route definiti nei prefissi del gruppo esterno.

Il prefisso può anche essere vuoto. Ciò può essere utile per l'aggiunta di metadati o filtri dell'endpoint a un gruppo di endpoint senza modificare il modello di route.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

L'aggiunta di filtri o metadati a un gruppo ha lo stesso comportamento dell'aggiunta singolarmente a ogni endpoint prima di aggiungere altri filtri o metadati che potrebbero essere stati aggiunti a un gruppo interno o a un endpoint specifico.

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

Nell'esempio precedente, il filtro esterno registra la richiesta in ingresso prima del filtro interno anche se è stato aggiunto secondo. Poiché i filtri sono stati applicati a gruppi diversi, l'ordine in cui sono stati aggiunti l'uno rispetto all'altro non è importante. I filtri dell'ordine vengono aggiunti se applicati allo stesso gruppo o allo stesso endpoint specifico.

Una richiesta per /outer/inner/ registrare quanto segue:

/outer group filter
/inner group filter
MapGet filter

Indicazioni sulle prestazioni per il routing

Quando un'app presenta problemi di prestazioni, il routing viene spesso sospettato come problema. Il routing è sospetto che framework come controller e Razor pagine segnalano la quantità di tempo impiegato all'interno del framework nei messaggi di registrazione. Quando si verifica una differenza significativa tra il tempo segnalato dai controller e il tempo totale della richiesta:

  • Gli sviluppatori eliminano il codice dell'app come origine del problema.
  • È comune presupporre che il routing sia la causa.

Il routing viene testato usando migliaia di endpoint. È improbabile che un'app tipica incontri un problema di prestazioni semplicemente essendo troppo grande. La causa radice più comune delle prestazioni di routing lente è in genere un middleware personalizzato con comportamento non valido.

Questo esempio di codice seguente illustra una tecnica di base per restringere l'origine del ritardo:

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

Routing temporizzato:

  • Interleave ogni middleware con una copia del middleware di intervallo illustrato nel codice precedente.
  • Aggiungere un identificatore univoco per correlare i dati di intervallo con il codice.

Si tratta di un modo di base per limitare il ritardo quando è significativo, ad esempio, più di 10ms. Sottraendo Time 2 dai Time 1 report il tempo trascorso all'interno del UseRouting middleware.

Il codice seguente usa un approccio più compatto al codice di intervallo precedente:

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

Funzionalità di routing potenzialmente costose

L'elenco seguente fornisce alcune informazioni dettagliate sulle funzionalità di routing relativamente costose rispetto ai modelli di route di base:

  • Espressioni regolari: è possibile scrivere espressioni regolari complesse o con tempi di esecuzione lunghi con una piccola quantità di input.
  • Segmenti complessi ({x}-{y}-{z}):
    • Sono notevolmente più costosi rispetto all'analisi di un segmento di percorso URL normale.
    • Comporta l'allocazione di molte più sottostringhe.
  • Accesso ai dati sincrono: molte app complesse hanno accesso al database come parte del routing. Usare punti di estendibilità come MatcherPolicy e EndpointSelectorContext, che sono asincroni.

Linee guida per tabelle di route di grandi dimensioni

Per impostazione predefinita, ASP.NET Core usa un algoritmo di routing che scambia la memoria per il tempo della CPU. Questo ha l'effetto interessante che il tempo di corrispondenza della route dipende solo dalla lunghezza del percorso di corrispondenza e non dal numero di route. Tuttavia, questo approccio può essere potenzialmente problematico in alcuni casi, quando l'app ha un numero elevato di route (nelle migliaia) e c'è una quantità elevata di prefissi variabili nelle route. Ad esempio, se le route hanno parametri nei segmenti iniziali della route, ad {parameter}/some/literalesempio .

È improbabile che un'app si verifichi in una situazione in cui si tratta di un problema a meno che:

  • L'app usa questo modello per un numero elevato di route.
  • Nell'app è presente un numero elevato di route.

Come determinare se un'app è in esecuzione nel problema della tabella di route di grandi dimensioni

  • Esistono due sintomi da cercare:
    • L'app è lenta per l'avvio nella prima richiesta.
      • Si noti che questa operazione è obbligatoria ma non sufficiente. Ci sono molti altri problemi non di route che possono causare un rallentamento dell'avvio dell'app. Verificare la condizione seguente per determinare con precisione che l'app si trova in questa situazione.
    • L'app usa molta memoria durante l'avvio e un dump della memoria mostra un numero elevato di Microsoft.AspNetCore.Routing.Matching.DfaNode istanze.

Come risolvere questo problema

Esistono diverse tecniche e ottimizzazioni che possono essere applicate alle route che miglioreranno in gran parte questo scenario:

  • Applicare vincoli di route ai parametri, ad esempio {parameter:int}, , {parameter:guid}{parameter:regex(\\d+)}e così via, laddove possibile.
    • Ciò consente all'algoritmo di routing di ottimizzare internamente le strutture usate per la corrispondenza e ridurre drasticamente la memoria usata.
    • Nella stragrande maggioranza dei casi questo sarà sufficiente per tornare a un comportamento accettabile.
  • Modificare le route per spostare i parametri in segmenti successivi nel modello.
    • In questo modo si riduce il numero di possibili "percorsi" in modo che corrispondano a un endpoint in base a un percorso.
  • Usare una route dinamica ed eseguire il mapping a un controller/pagina in modo dinamico.
    • A tale scopo, è possibile usare MapDynamicControllerRoute e MapDynamicPageRoute.

Linee guida per gli autori di librerie

Questa sezione contiene indicazioni per gli autori di librerie che si basano sul routing. Questi dettagli sono progettati per garantire che gli sviluppatori di app abbiano un'esperienza ottimale usando librerie e framework che estendono il routing.

Definire gli endpoint

Per creare un framework che usa il routing per la corrispondenza degli URL, iniziare definendo un'esperienza utente basata su UseEndpoints.

Do build on top of IEndpointRouteBuilder. In questo modo gli utenti possono comporre il framework con altre funzionalità di ASP.NET Core senza confusione. Ogni modello ASP.NET Core include il routing. Si supponga che il routing sia presente e familiare per gli utenti.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

DO restituisce un tipo di cemento sigillato da una chiamata a MapMyFramework(...) che implementa IEndpointConventionBuilder. La maggior parte dei metodi del framework Map... segue questo modello. Interfaccia IEndpointConventionBuilder :

  • Consente la composizione dei metadati.
  • È destinato a un'ampia gamma di metodi di estensione.

La dichiarazione del proprio tipo consente di aggiungere funzionalità specifiche del framework al generatore. È possibile eseguire il wrapping di un generatore dichiarato dal framework e inoltrare le chiamate.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

PRENDERE IN CONSIDERAZIONE la scrittura di un proprio EndpointDataSourceoggetto . EndpointDataSource è la primitiva di basso livello per dichiarare e aggiornare una raccolta di endpoint. EndpointDataSource è un'API potente usata dai controller e Razor dalle pagine.

I test di routing hanno un esempio di base di un'origine dati non aggiornata.

PRENDERE IN CONSIDERAZIONE l'implementazione GetGroupedEndpointsdi . In questo modo si ottiene il controllo completo sulle convenzioni di gruppo in esecuzione e sui metadati finali sugli endpoint raggruppati. In questo modo, ad esempio, le implementazioni personalizzate EndpointDataSource possono eseguire filtri endpoint aggiunti ai gruppi.

NON tentare di registrare un oggetto EndpointDataSource per impostazione predefinita. Richiedere agli utenti di registrare il framework in UseEndpoints. La filosofia del routing è che nulla è incluso per impostazione predefinita e che UseEndpoints è la posizione in cui registrare gli endpoint.

Creazione di middleware integrato nel routing

CONSIDERARE la definizione dei tipi di metadati come interfaccia.

DO consente di usare i tipi di metadati come attributo per classi e metodi.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Framework come controller e Razor pagine supportano l'applicazione di attributi di metadati a tipi e metodi. Se dichiari i tipi di metadati:

  • Renderli accessibili come attributi.
  • La maggior parte degli utenti ha familiarità con l'applicazione degli attributi.

La dichiarazione di un tipo di metadati come interfaccia aggiunge un altro livello di flessibilità:

  • Le interfacce sono componibili.
  • Gli sviluppatori possono dichiarare i propri tipi che combinano più criteri.

FARE in modo che sia possibile eseguire l'override dei metadati, come illustrato nell'esempio seguente:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Il modo migliore per seguire queste linee guida consiste nell'evitare di definire i metadati del marcatore:

  • Non cercare solo la presenza di un tipo di metadati.
  • Definire una proprietà sui metadati e controllare la proprietà .

La raccolta di metadati viene ordinata e supporta l'override in base alla priorità. Nel caso dei controller, i metadati nel metodo di azione sono più specifici.

FARE in modo che il middleware sia utile con e senza routing:

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

Come esempio di questa linea guida, prendere in considerazione il UseAuthorization middleware. Il middleware di autorizzazione consente di passare un criterio di fallback. I criteri di fallback, se specificati, si applicano a entrambi:

  • Endpoint senza criteri specificati.
  • Richieste che non corrispondono a un endpoint.

In questo modo il middleware di autorizzazione risulta utile al di fuori del contesto del routing. Il middleware di autorizzazione può essere usato per la programmazione middleware tradizionale.

Eseguire il debug della diagnostica

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

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

Risorse aggiuntive

Il routing è responsabile della corrispondenza delle richieste HTTP in ingresso e dell'invio di tali richieste agli endpoint eseguibili dell'app. Gli endpoint sono le unità di misura dell'app del codice di gestione delle richieste eseguibili. Gli endpoint vengono definiti nell'app e configurati all'avvio dell'app. Il processo di corrispondenza dell'endpoint può estrarre i valori dall'URL della richiesta e fornire tali valori per l'elaborazione della richiesta. Usando le informazioni sull'endpoint dall'app, il routing è anche in grado di generare URL che eseguono il mapping agli endpoint.

Le app possono configurare il routing usando:

Questo articolo illustra i dettagli di basso livello del routing ASP.NET Core. Per informazioni sulla configurazione del routing:

Nozioni fondamentali sul routing

Il codice seguente illustra un esempio di routing di base:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

L'esempio precedente include un singolo endpoint usando il MapGet metodo :

  • Quando viene inviata una richiesta HTTP GET all'URL /radice :
    • Il delegato della richiesta viene eseguito.
    • Hello World! viene scritto nella risposta HTTP.
  • Se il metodo di richiesta non GET è o l'URL radice non /è , non viene restituita alcuna corrispondenza di route e viene restituito un HTTP 404.

Il routing usa una coppia di middleware, registrati da UseRouting e UseEndpoints:

  • UseRouting aggiunge la corrispondenza della route alla pipeline middleware. Questo middleware esamina il set di endpoint definiti nell'app e seleziona la corrispondenza migliore in base alla richiesta.
  • UseEndpoints aggiunge l'esecuzione dell'endpoint alla pipeline middleware. Esegue il delegato associato all'endpoint selezionato.

Le app in genere non devono chiamare UseRouting o UseEndpoints. WebApplicationBuilder configura una pipeline middleware che esegue il wrapping del middleware aggiunto in Program.cs con UseRouting e UseEndpoints. Tuttavia, le app possono modificare l'ordine in cui UseRouting ed eseguire UseEndpoints chiamando questi metodi in modo esplicito. Ad esempio, il codice seguente effettua una chiamata esplicita a UseRouting:

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

Nel codice precedente:

  • La chiamata a app.Use registra un middleware personalizzato eseguito all'inizio della pipeline.
  • La chiamata a UseRouting configura il middleware corrispondente alla route da eseguire dopo il middleware personalizzato.
  • L'endpoint registrato con MapGet viene eseguito alla fine della pipeline.

Se l'esempio precedente non includeva una chiamata a UseRouting, il middleware personalizzato verrebbe eseguito dopo il middleware corrispondente alla route.

Endpoint

Il MapGet metodo viene usato per definire un endpoint. Un endpoint è un elemento che può essere:

  • Selezionato, associando l'URL e il metodo HTTP.
  • Eseguito eseguendo il delegato .

Gli endpoint che possono essere confrontati ed eseguiti dall'app vengono configurati in UseEndpoints. Ad esempio, MapGet, MapPoste metodi simili connettono i delegati di richiesta al sistema di routing. È possibile usare metodi aggiuntivi per connettere ASP.NET funzionalità del framework Core al sistema di routing:

L'esempio seguente mostra il routing con un modello di route più sofisticato:

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

La stringa /hello/{name:alpha} è un modello di route. Viene usato un modello di route per configurare la corrispondenza dell'endpoint. In questo caso, il modello corrisponde a:

  • UN URL come /hello/Docs
  • Qualsiasi percorso URL che inizia con /hello/ seguito da una sequenza di caratteri alfabetici. :alpha applica un vincolo di route che corrisponde solo ai caratteri alfabetici. I vincoli di route vengono illustrati più avanti in questo articolo.

Secondo segmento del percorso URL, {name:alpha}:

L'esempio seguente mostra il routing con controlli di integrità e autorizzazione:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

L'esempio precedente illustra come:

  • Il middleware di autorizzazione può essere usato con il routing.
  • Gli endpoint possono essere usati per configurare il comportamento di autorizzazione.

La MapHealthChecks chiamata aggiunge un endpoint di controllo integrità. Il concatenamento RequireAuthorization a questa chiamata collega un criterio di autorizzazione all'endpoint.

La chiamata UseAuthentication e UseAuthorization aggiunge il middleware di autenticazione e autorizzazione. Questi middleware vengono posizionati tra UseRouting e UseEndpoints in modo che possano:

  • Vedere quale endpoint è stato selezionato da UseRouting.
  • Applicare un criterio di autorizzazione prima UseEndpoints dell'invio all'endpoint.

Metadati dell'endpoint

Nell'esempio precedente sono presenti due endpoint, ma solo l'endpoint di controllo integrità ha un criterio di autorizzazione associato. Se la richiesta corrisponde all'endpoint del controllo integrità, /healthzviene eseguito un controllo di autorizzazione. Ciò dimostra che gli endpoint possono avere dati aggiuntivi collegati. Questi dati aggiuntivi sono denominati metadati dell'endpoint:

  • I metadati possono essere elaborati tramite middleware compatibile con il routing.
  • I metadati possono essere di qualsiasi tipo .NET.

Concetti relativi al routing

Il sistema di routing si basa sulla pipeline middleware aggiungendo il concetto avanzato di endpoint . Gli endpoint rappresentano unità di funzionalità dell'app distinte l'una dall'altra in termini di routing, autorizzazione e qualsiasi numero di sistemi ASP.NET Core.

definizione dell'endpoint principale ASP.NET

Un endpoint ASP.NET Core è:

Il codice seguente illustra come recuperare ed esaminare l'endpoint corrispondente alla richiesta corrente:

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

L'endpoint, se selezionato, può essere recuperato da HttpContext. È possibile controllarne le proprietà. Gli oggetti endpoint non sono modificabili e non possono essere modificati dopo la creazione. Il tipo di endpoint più comune è .RouteEndpoint RouteEndpoint include informazioni che consentono di selezionarla dal sistema di routing.

Nel codice precedente, app. Usa configura un middleware inline.

Il codice seguente mostra che, a seconda della posizione in cui app.Use viene chiamato nella pipeline, potrebbe non esserci un endpoint:

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

Nell'esempio precedente vengono aggiunte Console.WriteLine istruzioni che indicano se è stato selezionato o meno un endpoint. Per maggiore chiarezza, l'esempio assegna un nome visualizzato all'endpoint fornito / .

L'esempio precedente include anche chiamate a UseRouting e UseEndpoints per controllare esattamente quando questi middleware vengono eseguiti all'interno della pipeline.

Esecuzione di questo codice con un URL di / visualizzazione:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

L'esecuzione di questo codice con qualsiasi altro URL viene visualizzata:

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Questo output dimostra che:

  • L'endpoint è sempre Null prima UseRouting di essere chiamato.
  • Se viene trovata una corrispondenza, l'endpoint non è Null tra UseRouting e UseEndpoints.
  • Il UseEndpoints middleware è terminale quando viene trovata una corrispondenza. Il middleware del terminale viene definito più avanti in questo articolo.
  • Middleware dopo UseEndpoints l'esecuzione solo quando non viene trovata alcuna corrispondenza.

Il UseRouting middleware usa il SetEndpoint metodo per collegare l'endpoint al contesto corrente. È possibile sostituire il middleware con la UseRouting logica personalizzata e ottenere comunque i vantaggi dell'uso degli endpoint. Gli endpoint sono primitivi di basso livello come middleware e non sono associati all'implementazione del routing. La maggior parte delle app non deve sostituire UseRouting con la logica personalizzata.

Il UseEndpoints middleware è progettato per essere usato in parallelo con il UseRouting middleware. La logica di base per eseguire un endpoint non è complicata. Usare GetEndpoint per recuperare l'endpoint e quindi richiamarne la RequestDelegate proprietà.

Il codice seguente illustra come il middleware può influenzare o reagire al routing:

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

L'esempio precedente illustra due concetti importanti:

  • Il middleware può essere eseguito prima UseRouting di modificare i dati su cui opera il routing.
  • Il middleware può essere eseguito tra UseRouting e UseEndpoints per elaborare i risultati del routing prima dell'esecuzione dell'endpoint.
    • Middleware eseguito tra UseRouting e UseEndpoints:
      • In genere controlla i metadati per comprendere gli endpoint.
      • Spesso prende decisioni di sicurezza, come fatto da UseAuthorization e UseCors.
    • La combinazione di middleware e metadati consente di configurare i criteri per endpoint.

Il codice precedente mostra un esempio di middleware personalizzato che supporta i criteri per endpoint. Il middleware scrive un log di controllo dell'accesso ai dati sensibili nella console. Il middleware può essere configurato per controllare un endpoint con i RequiresAuditAttribute metadati. Questo esempio illustra un modello di consenso esplicito in cui vengono controllati solo gli endpoint contrassegnati come sensibili. È possibile definire questa logica inversa, controllando tutto ciò che non è contrassegnato come sicuro, ad esempio. Il sistema di metadati dell'endpoint è flessibile. Questa logica può essere progettata in qualsiasi modo adatto al caso d'uso.

Il codice di esempio precedente è progettato per illustrare i concetti di base degli endpoint. L'esempio non è destinato all'uso in produzione. Una versione più completa di un middleware del log di controllo è la seguente:

  • Accedere a un file o a un database.
  • Includere dettagli, ad esempio l'utente, l'indirizzo IP, il nome dell'endpoint sensibile e altro ancora.

I metadati dei criteri RequiresAuditAttribute di controllo vengono definiti come un oggetto per un Attribute uso più semplice con framework basati su classi, ad esempio controller e SignalR. Quando si usa la route al codice:

  • I metadati vengono associati a un'API del generatore.
  • I framework basati su classi includono tutti gli attributi nel metodo e nella classe corrispondenti durante la creazione di endpoint.

Le procedure consigliate per i tipi di metadati sono di definirle come interfacce o attributi. Le interfacce e gli attributi consentono il riutilizzo del codice. Il sistema di metadati è flessibile e non impone alcuna limitazione.

Confrontare il middleware del terminale con il routing

L'esempio seguente illustra sia il middleware del terminale che il routing:

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Lo stile del middleware visualizzato con Approach 1: è il middleware del terminale. Si chiama middleware del terminale perché esegue un'operazione corrispondente:

  • L'operazione di corrispondenza nell'esempio precedente è Path == "/" relativa al middleware e Path == "/Routing" al routing.
  • Quando una corrispondenza ha esito positivo, esegue alcune funzionalità e restituisce, anziché richiamare il next middleware.

Si tratta di un middleware terminale perché termina la ricerca, esegue alcune funzionalità e quindi restituisce.

L'elenco seguente confronta il middleware del terminale con il routing:

  • Entrambi gli approcci consentono di terminare la pipeline di elaborazione:
    • Il middleware termina la pipeline restituendo anziché richiamando next.
    • Gli endpoint sono sempre terminale.
  • Il middleware del terminale consente di posizionare il middleware in una posizione arbitraria nella pipeline:
    • Gli endpoint sono eseguiti nella posizione di UseEndpoints.
  • Il middleware del terminale consente al codice arbitrario di determinare quando il middleware corrisponde:
    • Il codice personalizzato di corrispondenza della route può essere dettagliato e difficile da scrivere correttamente.
    • Il routing offre soluzioni semplici per le app tipiche. La maggior parte delle app non richiede codice di corrispondenza di route personalizzato.
  • Interfaccia degli endpoint con middleware, UseAuthorization ad esempio e UseCors.
    • L'uso di un middleware del terminale con UseAuthorization o UseCors richiede l'interfaccia manuale con il sistema di autorizzazione.

Un endpoint definisce entrambi:

  • Delegato per elaborare le richieste.
  • Raccolta di metadati arbitrari. I metadati vengono usati per implementare problematiche trasversali in base ai criteri e alla configurazione collegati a ogni endpoint.

Il middleware del terminale può essere uno strumento efficace, ma può richiedere:

  • Quantità significativa di codifica e test.
  • Integrazione manuale con altri sistemi per ottenere il livello di flessibilità desiderato.

Prendere in considerazione l'integrazione con il routing prima di scrivere un middleware del terminale.

Il middleware del terminale esistente che si integra con Map o MapWhen in genere può essere trasformato in un endpoint compatibile con il routing. MapHealthChecks illustra il modello per router-ware:

Il codice seguente illustra l'uso di MapHealthChecks:

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

L'esempio precedente mostra perché la restituzione dell'oggetto builder è importante. La restituzione dell'oggetto builder consente allo sviluppatore di app di configurare criteri come l'autorizzazione per l'endpoint. In questo esempio, il middleware dei controlli di integrità non ha alcuna integrazione diretta con il sistema di autorizzazione.

Il sistema di metadati è stato creato in risposta ai problemi riscontrati dagli autori dell'estendibilità usando il middleware del terminale. È problematico per ogni middleware implementare la propria integrazione con il sistema di autorizzazione.

Corrispondenza URL

  • Processo in base al quale il routing corrisponde a una richiesta in ingresso a un endpoint.
  • Si basa sui dati nel percorso e nelle intestazioni URL.
  • Può essere esteso per prendere in considerazione tutti i dati nella richiesta.

Quando viene eseguito un middleware di routing, imposta un valore Endpoint e indirizza i valori a una funzionalità di richiesta nella HttpContext richiesta corrente:

  • La chiamata a HttpContext.GetEndpoint ottiene l'endpoint.
  • HttpRequest.RouteValues ottiene la raccolta dei valori di route.

Il middleware viene eseguito dopo il middleware di routing può esaminare l'endpoint ed eseguire azioni. Ad esempio, un middleware di autorizzazione può interrogare la raccolta di metadati dell'endpoint per un criterio di autorizzazione. Dopo l'esecuzione di tutto il middleware nella pipeline di elaborazione della richiesta, viene richiamato il delegato dell'endpoint selezionato.

Il sistema di routing nell'endpoint di routing è responsabile di tutte le decisioni relative all'invio. Poiché il middleware applica i criteri in base all'endpoint selezionato, è importante:

  • Qualsiasi decisione che può influire sull'invio o sull'applicazione dei criteri di sicurezza viene effettuata all'interno del sistema di routing.

Avviso

Per garantire la compatibilità con le versioni precedenti, quando viene eseguito un delegato dell'endpoint Controller o Razor Pages, le proprietà di RouteContext.RouteData vengono impostate sui valori appropriati in base all'elaborazione della richiesta eseguita finora.

Il RouteContext tipo verrà contrassegnato come obsoleto in una versione futura:

  • Eseguire la migrazione RouteData.Values a HttpRequest.RouteValues.
  • Eseguire la migrazione RouteData.DataTokens per recuperare IDataTokensMetadata dai metadati dell'endpoint.

La corrispondenza degli URL opera in un set configurabile di fasi. In ogni fase, l'output è un set di corrispondenze. Il set di corrispondenze può essere limitato ulteriormente dalla fase successiva. L'implementazione del routing non garantisce un ordine di elaborazione per gli endpoint corrispondenti. Tutte le possibili corrispondenze vengono elaborate contemporaneamente. Le fasi di corrispondenza dell'URL si verificano nell'ordine seguente. ASP.NET Core:

  1. Elabora il percorso URL rispetto al set di endpoint e ai relativi modelli di route, raccogliendo tutte le corrispondenze.
  2. Accetta l'elenco precedente e rimuove le corrispondenze che hanno esito negativo con vincoli di route applicati.
  3. Accetta l'elenco precedente e rimuove le corrispondenze che non superano il set di MatcherPolicy istanze.
  4. Usa per EndpointSelector prendere una decisione finale dall'elenco precedente.

L'elenco degli endpoint è prioritario in base a:

Tutti gli endpoint corrispondenti vengono elaborati in ogni fase fino a quando non viene raggiunto .EndpointSelector è EndpointSelector la fase finale. Sceglie l'endpoint con priorità più alta tra le corrispondenze come corrispondenza migliore. Se sono presenti altre corrispondenze con la stessa priorità della corrispondenza migliore, viene generata un'eccezione di corrispondenza ambigua.

La precedenza della route viene calcolata in base a un modello di route più specifico a cui viene assegnata una priorità più alta. Si considerino ad esempio i modelli /hello e /{message}:

  • Entrambi corrispondono al percorso /helloURL .
  • /hello è più specifico e quindi priorità più alta.

In generale, la precedenza di route fa un buon lavoro per scegliere la corrispondenza migliore per i tipi di schemi URL usati in pratica. Usare Order solo quando necessario per evitare ambiguità.

A causa dei tipi di estendibilità forniti dal routing, non è possibile che il sistema di routing calcoli in anticipo le route ambigue. Si consideri un esempio, ad esempio i modelli /{message:alpha} di route e /{message:int}:

  • Il alpha vincolo corrisponde solo ai caratteri alfabetici.
  • Il int vincolo corrisponde solo ai numeri.
  • Questi modelli hanno la stessa precedenza di route, ma non esiste un singolo URL che corrispondono entrambi.
  • Se il sistema di routing ha segnalato un errore di ambiguità all'avvio, blocca questo caso d'uso valido.

Avviso

L'ordine delle operazioni all'interno UseEndpoints non influisce sul comportamento del routing, con un'eccezione. MapControllerRoute e MapAreaRoute assegnano automaticamente un valore di ordine ai relativi endpoint in base all'ordine in cui vengono richiamati. Questo simula il comportamento a lungo termine dei controller senza che il sistema di routing fornisca le stesse garanzie delle implementazioni di routing meno recenti.

Endpoint routing in ASP.NET Core:

  • Non ha il concetto di itinerari.
  • Non fornisce garanzie di ordinamento. Tutti gli endpoint vengono elaborati contemporaneamente.

Precedenza del modello di route e ordine di selezione dell'endpoint

La precedenza del modello di route è un sistema che assegna a ogni modello di route un valore in base al modo in cui è specifico. Precedenza del modello di route:

  • Evita la necessità di modificare l'ordine degli endpoint nei casi comuni.
  • Tenta di corrispondere alle aspettative di buon senso del comportamento di routing.

Si considerino ad esempio modelli /Products/List e /Products/{id}. Sarebbe ragionevole presupporre che /Products/List sia una corrispondenza migliore rispetto /Products/{id} al percorso /Products/ListURL . Ciò funziona perché il segmento letterale /List viene considerato avere una precedenza migliore rispetto al segmento di /{id}parametro .

I dettagli sul funzionamento della precedenza sono associati alla definizione dei modelli di route:

  • I modelli con più segmenti sono considerati più specifici.
  • Un segmento con testo letterale è considerato più specifico di un segmento di parametro.
  • Un segmento di parametro con un vincolo viene considerato più specifico di uno senza.
  • Un segmento complesso viene considerato specifico come segmento di parametro con un vincolo.
  • I parametri catch-all sono i meno specifici. Per informazioni importanti sulle route catch-all, vedere catch-all nella sezione Modelli di route.

Concetti relativi alla generazione di URL

Generazione url:

  • Processo in base al quale il routing può creare un percorso URL basato su un set di valori di route.
  • Consente una separazione logica tra gli endpoint e gli URL che vi accedono.

Il routing degli endpoint include l'API LinkGenerator . LinkGeneratorè un servizio singleton disponibile dall'inserimento delle dipendenze. L'API LinkGenerator può essere usata al di fuori del contesto di una richiesta in esecuzione. Mvc.IUrlHelper e scenari che si basano su IUrlHelper, ad esempio helper tag, helper HTML e risultati azione, usano l'API LinkGenerator internamente per fornire funzionalità di generazione di collegamenti.

Il generatore di collegamenti si basa sui concetti di indirizzo e di schemi di indirizzi. Lo schema di indirizzi consente di determinare gli endpoint che devono essere considerati per la generazione dei collegamenti. Ad esempio, gli scenari di nome della route e valori di route con cui molti utenti hanno familiarità con i controller e Razor le pagine vengono implementati come schema di indirizzi.

Il generatore di collegamenti può collegarsi ai controller e Razor alle pagine tramite i metodi di estensione seguenti:

Gli overload di questi metodi accettano argomenti che includono .HttpContext Questi metodi sono funzionalmente equivalenti a Url.Action e Url.Page, ma offrono flessibilità e opzioni aggiuntive.

I GetPath* metodi sono più simili a Url.Action e Url.Page, in quanto generano un URI contenente un percorso assoluto. I metodi GetUri* generano sempre un URI assoluto contenente uno schema e un host. I metodi che accettano HttpContext generano un URI nel contesto della richiesta in esecuzione. I valori di route di ambiente , il percorso di base URL, lo schema e l'host della richiesta in esecuzione vengono usati a meno che non venga sottoposto a override.

LinkGenerator viene chiamato con un indirizzo. La generazione di un URI viene eseguita in due passaggi:

  1. Un indirizzo viene associato a un elenco di endpoint che corrispondono all'indirizzo.
  2. RoutePattern di ogni endpoint viene valutato fino a quando non viene individuato un formato di route che corrisponde ai valori specificati. L'output risultante viene unito alle altre parti dell'URI specificate nel generatore di collegamenti e restituito.

I metodi forniti da LinkGenerator supportano le funzionalità di generazione di collegamenti standard per tutti i tipi di indirizzi. Il modo più pratico per usare il generatore di collegamenti è tramite metodi di estensione che eseguono operazioni per un tipo di indirizzo specifico:

Metodo di estensione Descrizione
GetPathByAddress Genera un URI con un percorso assoluto in base ai valori specificati.
GetUriByAddress Genera un URI assoluto in base ai valori specificati.

Avviso

Prestare attenzione alle implicazioni seguenti della chiamata ai metodi LinkGenerator:

  • Usare i metodi di estensione GetUri* con cautela in una configurazione di app che non convalida l'intestazione Host delle richieste in ingresso. Se l'intestazione delle richieste in ingresso non viene convalidata, l'input Host di richiesta non attendibile può essere inviato al client negli URI di una visualizzazione o di una pagina. È consigliabile che in tutte le app di produzione il server sia configurato per la convalida dell'intestazione Host rispetto ai valori validi noti.

  • Usare LinkGenerator con cautela nel middleware in associazione a Map o MapWhen. Map* modifica il percorso di base della richiesta in esecuzione, che ha effetto sull'output della generazione di collegamenti. Tutte le API LinkGenerator consentono di specificare un percorso di base. Specificare un percorso di base vuoto per annullare l'effetto Map* sulla generazione del collegamento.

Esempio di middleware

Nell'esempio seguente un middleware usa l'API LinkGenerator per creare un collegamento a un metodo di azione che elenca i prodotti dell'archivio. L'uso del generatore di collegamenti inserendolo in una classe e chiamando GenerateLink è disponibile per qualsiasi classe in un'app:

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

Modelli di route

I token all'interno {} definiscono i parametri di route associati se la route corrisponde. È possibile definire più parametri di route in un segmento di route, ma i parametri di route devono essere separati da un valore letterale. Ad esempio:

{controller=Home}{action=Index}

non è una route valida, perché non esiste alcun valore letterale tra {controller} e {action}. I parametri di route devono avere un nome e possono avere attributi aggiuntivi specificati.

Il testo letterale diverso dai parametri di route, ad esempio {id}, e il separatore di percorso / devono corrispondere al testo nell'URL. La corrispondenza del testo non fa distinzione tra maiuscole e minuscole e si basa sulla rappresentazione decodificata del percorso dell'URL. Per trovare una corrispondenza con un delimitatore { di parametro di route letterale o }, eseguire l'escape del delimitatore ripetendo il carattere. Ad esempio {{ o }}.

Asterisco * o asterisco **doppio:

  • Può essere usato come prefisso per un parametro di route da associare all'URI rest .
  • Vengono chiamati parametri catch-all . Ad esempio, blog/{**slug}:
    • Trova la corrispondenza con qualsiasi URI che inizia con blog/ e ha qualsiasi valore dopo di esso.
    • Il valore seguente blog/ viene assegnato al valore di route slug .

Avviso

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

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

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

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

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

I parametri catch-all possono anche corrispondere alla stringa vuota.

Il parametro catch-all esegue l'escape dei caratteri appropriati quando viene usata la route per generare un URL, inclusi i caratteri separatori / di percorso. Ad esempio, la route foo/{*path} con i valori di route { path = "my/path" } genera foo/my%2Fpath. Si noti la barra di escape. Per eseguire il round trip dei caratteri di separatore di percorso, usare il prefisso del parametro di route **. La route foo/{**path} con { path = "my/path" } genera foo/my/path.

I modelli di URL che tentano di acquisire un nome file con un'estensione facoltativa hanno considerazioni aggiuntive. Considerare ad esempio il modello files/{filename}.{ext?}. Se esistono i valori per filename e ext, vengono popolati entrambi i valori. Se nell'URL esiste solo un valore per filename , la route corrisponde perché l'elemento finale . è facoltativo. Gli URL seguenti corrispondono a questa route:

  • /files/myFile.txt
  • /files/myFile

I parametri di route possono avere valori predefiniti, definiti specificando il valore predefinito dopo il nome del parametro, separato da un segno di uguale (=). Ad esempio, {controller=Home} definisce Home come valore predefinito per controller. Il valore predefinito viene usato se nell'URL non è presente alcun valore per il parametro. I parametri di route vengono resi facoltativi aggiungendo un punto interrogativo (?) alla fine del nome del parametro. Ad esempio: id?. La differenza tra i valori facoltativi e i parametri di route predefiniti è:

  • Un parametro di route con un valore predefinito produce sempre un valore.
  • Un parametro facoltativo ha un valore solo quando un valore viene fornito dall'URL della richiesta.

I parametri di route possono presentare dei vincoli che devono corrispondere al valore di route associato dall'URL. L'aggiunta : del nome del vincolo e dopo il nome del parametro di route specifica un vincolo inline su un parametro di route. Se il vincolo richiede argomenti, vengono racchiusi tra parentesi (...) dopo il nome del vincolo. È possibile specificare più vincoli inline aggiungendo un altro : nome di vincolo e .

Il nome del vincolo e gli argomenti vengono passati al servizio IInlineConstraintResolver per creare un'istanza di IRouteConstraint da usare nell'elaborazione dell'URL. Il modello di route blog/{article:minlength(10)} specifica ad esempio un vincolo minlength con l'argomento 10. Per altre informazioni sui vincoli di route e un elenco dei vincoli forniti dal framework, vedere la sezione Vincoli di route.

I parametri di route possono avere anche trasformatori di parametro. I trasformatori di parametro trasformano il valore di un parametro durante la generazione di collegamenti e azioni e pagine corrispondenti agli URL. Analogamente ai vincoli, i trasformatori di parametro possono essere aggiunti inline a un parametro di route aggiungendo un : nome di trasformatore e dopo il nome del parametro di route. Ad esempio, il modello di route blog/{article:slugify} specifica un trasformatore slugify. Per altre informazioni sui trasformatori di parametri, vedere la sezione Trasformatori di parametro.

La tabella seguente illustra modelli di route di esempio e il relativo comportamento:

Modello di route URI corrispondente di esempio L'URI della richiesta
hello /hello Verifica la corrispondenza solo del singolo percorso /hello.
{Page=Home} / Verifica la corrispondenza e imposta Page su Home.
{Page=Home} /Contact Verifica la corrispondenza e imposta Page su Contact.
{controller}/{action}/{id?} /Products/List Esegue il mapping al controller Products e all'azione List.
{controller}/{action}/{id?} /Products/Details/123 Esegue il Products mapping al controller e Details all'azione conid impostato su 123.
{controller=Home}/{action=Index}/{id?} / Esegue il mapping al controller e Index al Home metodo . id viene ignorato.
{controller=Home}/{action=Index}/{id?} /Products Esegue il mapping al controller e Index al Products metodo . id viene ignorato.

L'uso di un modello è in genere l'approccio più semplice al routing. I vincoli e le impostazioni predefinite possono essere specificati anche all'esterno del modello di route.

Segmenti complessi

I segmenti complessi vengono elaborati associando delimitatori letterali da destra a sinistra in modo non greedy . Ad esempio, [Route("/a{b}c{d}")] è un segmento complesso. I segmenti complessi funzionano in modo particolare che devono essere compresi per usarli correttamente. L'esempio in questa sezione illustra perché i segmenti complessi funzionano correttamente solo quando il testo del delimitatore non viene visualizzato all'interno dei valori dei parametri. L'uso di un'espressione regolare e quindi l'estrazione manuale dei valori è necessaria per casi più complessi.

Avviso

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

Si tratta di un riepilogo dei passaggi che il routing esegue con il modello /a{b}c{d} e il percorso /abcdURL . Viene | usato per visualizzare il funzionamento dell'algoritmo:

  • Il primo valore letterale, da destra a sinistra, è c. Quindi /abcd viene eseguita la ricerca da destra e trova /ab|c|d.
  • Tutti gli elementi a destra (d) vengono ora confrontati con il parametro {d}di route .
  • Il valore letterale successivo, da destra a sinistra, è a. Quindi /ab|c|d viene eseguita una ricerca a partire dal punto in cui è stato interrotto, quindi a viene trovato /|a|b|c|d.
  • Il valore a destra (b) viene ora confrontato con il parametro {b}di route .
  • Nessun testo rimanente e nessun modello di route rimanente, quindi si tratta di una corrispondenza.

Di seguito è riportato un esempio di caso negativo usando lo stesso modello /a{b}c{d} e il percorso /aabcdURL . Viene | usato per visualizzare il funzionamento dell'algoritmo. Questo caso non è una corrispondenza, spiegata dallo stesso algoritmo:

  • Il primo valore letterale, da destra a sinistra, è c. Quindi /aabcd viene eseguita la ricerca da destra e trova /aab|c|d.
  • Tutti gli elementi a destra (d) vengono ora confrontati con il parametro {d}di route .
  • Il valore letterale successivo, da destra a sinistra, è a. Quindi /aab|c|d viene eseguita una ricerca a partire dal punto in cui è stato interrotto, quindi a viene trovato /a|a|b|c|d.
  • Il valore a destra (b) viene ora confrontato con il parametro {b}di route .
  • A questo punto è presente testo arimanente, ma l'algoritmo ha esaurito il modello di route da analizzare, quindi non si tratta di una corrispondenza.

Poiché l'algoritmo corrispondente non è greedy:

  • Corrisponde alla quantità minima di testo possibile in ogni passaggio.
  • Ogni caso in cui il valore del delimitatore venga visualizzato all'interno dei valori dei parametri non corrisponde.

Le espressioni regolari offrono un maggiore controllo sul comportamento di corrispondenza.

La corrispondenza Greedy, nota anche come corrispondenza differita, corrisponde alla stringa più grande possibile. Non greedy corrisponde alla stringa più piccola possibile.

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;

Vincoli della route

I vincoli di route vengono eseguiti quando si verifica una corrispondenza nell'URL in ingresso e il percorso URL viene suddiviso in valori di route in formato token. I vincoli di route in genere controllano il valore di route associato tramite il modello di route e prendere una decisione vera o falsa circa se il valore è accettabile. Alcuni vincoli di route usano i dati all'esterno del valore di route per stabilire se la richiesta può essere instradata. Ad esempio, HttpMethodRouteConstraint può accettare o rifiutare una richiesta in base al relativo verbo HTTP. I vincoli vengono usati nelle richieste del routing e nella generazione di collegamenti.

Avviso

Non usare i vincoli per la convalida dell'input. Se i vincoli vengono usati per la convalida dell'input, l'input non valido restituisce una 404 risposta non trovata. L'input non valido deve produrre una 400 richiesta non valida con un messaggio di errore appropriato. I vincoli di route vengono usati per evitare ambiguità tra route simili, non per convalidare gli input per una route specifica.

La tabella seguente illustra i vincoli di route di esempio e il relativo comportamento previsto:

vincolo Esempio Esempi di corrispondenza Note
int {id:int} 123456789, -123456789 Corrisponde a qualsiasi numero intero
bool {active:bool} true, FALSE Corrisponde true a o false. Nessuna distinzione tra maiuscole e minuscole
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Corrisponde a un valore valido DateTime nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
decimal {price:decimal} 49.99, -1,000.01 Corrisponde a un valore valido decimal nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
double {weight:double} 1.234, -1,001.01e8 Corrisponde a un valore valido double nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
float {weight:float} 1.234, -1,001.01e8 Corrisponde a un valore valido float nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Corrisponde a un valore Guid valido
long {ticks:long} 123456789, -123456789 Corrisponde a un valore long valido
minlength(value) {username:minlength(4)} Rick La stringa deve contenere almeno 4 caratteri
maxlength(value) {filename:maxlength(8)} MyFile La stringa non deve contenere più di 8 caratteri
length(length) {filename:length(12)} somefile.txt La stringa deve contenere esattamente 12 caratteri
length(min,max) {filename:length(8,16)} somefile.txt La stringa deve contenere almeno 8 e non più di 16 caratteri
min(value) {age:min(18)} 19 Il valore intero deve essere almeno 18
max(value) {age:max(120)} 91 Il valore intero non deve essere superiore a 120
range(min,max) {age:range(18,120)} 91 Il valore intero deve essere almeno 18 ma non più di 120
alpha {name:alpha} Rick La stringa deve essere costituita da uno o più caratteri a-z alfabetici e senza distinzione tra maiuscole e minuscole.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 La stringa deve corrispondere all'espressione regolare. Vedere suggerimenti sulla definizione di un'espressione regolare.
required {name:required} Rick Usato per imporre che un valore diverso da un parametro sia presente durante la generazione dell'URL

Avviso

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

Più vincoli delimitati da due punti possono essere applicati a un singolo parametro. Ad esempio il vincolo seguente limita un parametro a un valore intero maggiore o uguale a 1:

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Avviso

I vincoli di route che verificano l'URL e vengono convertiti in un tipo CLR usano sempre le impostazioni cultura invarianti. Ad esempio, la conversione nel tipo int CLR o DateTime. Questi vincoli presuppongono che l'URL non sia localizzabile. I vincoli di route specificati dal framework non modificano i valori archiviati nei valori di route. Tutti i valori di route analizzati dall'URL vengono archiviati come stringhe. Ad esempio, il vincolo float prova a convertire il valore di route in un valore float, ma il valore convertito viene usato solo per verificare che può essere convertito in un valore float.

Espressioni regolari nei vincoli

Avviso

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

Le espressioni regolari possono essere specificate come vincoli inline usando il vincolo di regex(...) route. I metodi nella MapControllerRoute famiglia accettano anche un valore letterale oggetto di vincoli. Se viene utilizzata la maschera, i valori stringa vengono interpretati come espressioni regolari.

Il codice seguente usa un vincolo regex inline:

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

Il codice seguente usa un valore letterale oggetto per specificare un vincolo regex:

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

Il framework di ASP.NET Core aggiunge RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant al costruttore di espressioni regolari. Per una descrizione di questi membri, vedere RegexOptions.

Le espressioni regolari usano delimitatori e token simili a quelli usati dal routing e dal linguaggio C#. Per i token di espressione è necessario aggiungere caratteri di escape. Per usare l'espressione ^\d{3}-\d{2}-\d{4}$ regolare in un vincolo inline, usare una delle opzioni seguenti:

  • Sostituire i \ caratteri specificati nella stringa come \\ caratteri nel file di origine C# per eseguire l'escape del carattere di escape della \ stringa.
  • Valori letterali stringa verbatim.

Per eseguire l'escape dei caratteri {delimitatori dei parametri di routing , [}, , ]double i caratteri nell'espressione, ad esempio , [[{{}}, , . ]] La tabella seguente mostra un'espressione regolare e la relativa versione con escape:

Espressione regolare Espressione regolare preceduta da escape
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Le espressioni regolari usate nel routing spesso iniziano con il ^ carattere e corrispondono alla posizione iniziale della stringa. Le espressioni terminano spesso con il $ carattere e corrispondono alla fine della stringa. I ^ caratteri e $ assicurano che l'espressione regolare corrisponda all'intero valore del parametro di route. Senza i ^ caratteri e $ , l'espressione regolare corrisponde a qualsiasi sottostringa all'interno della stringa, spesso indesiderata. La tabella seguente fornisce esempi e spiega il motivo per cui corrispondono o non corrispondono:

Espressione String Match Commento
[a-z]{2} hello Corrispondenze di sottostringhe
[a-z]{2} 123abc456 Corrispondenze di sottostringhe
[a-z]{2} mz Corrisponde all'espressione
[a-z]{2} MZ Senza distinzione maiuscole/minuscole
^[a-z]{2}$ hello No Vedere ^ e $ sopra
^[a-z]{2}$ 123abc456 No Vedere ^ e $ sopra

Per altre informazioni sulla sintassi delle espressioni regolari, vedere Espressioni regolari di .NET Framework.

Per limitare un parametro a un set noto di valori possibili, usare un'espressione regolare. Ad esempio, {action:regex(^(list|get|create)$)} verifica la corrispondenza del valore di route action solo con list, get o create. Se viene passata nel dizionario di vincoli, la stringa ^(list|get|create)$ è equivalente. I vincoli passati nel dizionario vincoli che non corrispondono a uno dei vincoli noti vengono considerati anche come espressioni regolari. I vincoli passati all'interno di un modello che non corrispondono a uno dei vincoli noti non vengono considerati come espressioni regolari.

Vincoli di route personalizzati

È possibile creare vincoli di route personalizzati implementando l'interfaccia IRouteConstraint . L'interfaccia IRouteConstraint contiene Match, che restituisce true se il vincolo è soddisfatto e false in caso contrario.

I vincoli di route personalizzati sono raramente necessari. Prima di implementare un vincolo di route personalizzato, prendere in considerazione alternative, ad esempio l'associazione di modelli.

La cartella ASP.NET vincoli principali fornisce esempi validi di creazione di vincoli. Ad esempio, GuidRouteConstraint.

Per usare un oggetto personalizzato IRouteConstraint, il tipo di vincolo di route deve essere registrato con l'app ConstraintMap nel contenitore del servizio. ConstraintMap è un dizionario che esegue il mapping delle chiavi dei vincoli di route alle implementazioni di IRouteConstraint che convalidano tali vincoli. Un'app ConstraintMap può essere aggiornata in Program.cs come parte di una AddRouting chiamata o configurando RouteOptions direttamente con builder.Services.Configure<RouteOptions>. Ad esempio:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

Il vincolo precedente viene applicato nel codice seguente:

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

L'implementazione di NoZeroesRouteConstraint impedisce l'uso 0 in un parametro di route:

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Avviso

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

Il codice precedente:

  • Impedisce 0 nel {id} segmento della route.
  • Viene illustrato come fornire un esempio di base dell'implementazione di un vincolo personalizzato. Non deve essere usato in un'app di produzione.

Il codice seguente è un approccio migliore per impedire l'elaborazione di un oggetto id contenente un 0 oggetto :

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

Il codice precedente presenta i vantaggi seguenti rispetto all'approccio NoZeroesRouteConstraint :

  • Non richiede un vincolo personalizzato.
  • Restituisce un errore più descrittivo quando il parametro di route include 0.

Trasformatori di parametro

I trasformatori di parametro:

Ad esempio, un trasformatore di parametro slugify personalizzato nel modello di route blog\{article:slugify} con Url.Action(new { article = "MyTestArticle" }) genera blog\my-test-article.

Si consideri l'implementazione seguente IOutboundParameterTransformer:

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value is null)
        {
            return null;
        }

        return Regex.Replace(
            value.ToString()!,
                "([a-z])([A-Z])",
            "$1-$2",
            RegexOptions.CultureInvariant,
            TimeSpan.FromMilliseconds(100))
            .ToLowerInvariant();
    }
}

Per usare un trasformatore di parametro in un modello di route, configurarlo usando ConstraintMap in Program.cs:

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

Il framework ASP.NET Core usa trasformatori di parametro per trasformare l'URI in cui viene risolto un endpoint. Ad esempio, i trasformatori di parametro trasformano i valori di route usati per trovare la corrispondenza con un areaoggetto , controlleraction, e page:

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

Con il modello di route precedente, l'azione SubscriptionManagementController.GetAll viene confrontata con l'URI /subscription-management/get-all. Un trasformatore di parametro non modifica i valori della route usati per generare un collegamento. Ad esempio, Url.Action("GetAll", "SubscriptionManagement") restituisce /subscription-management/get-all.

ASP.NET Core fornisce convenzioni API per l'uso di trasformatori di parametri con route generate:

Riferimento per la generazione di URL

Questa sezione contiene un riferimento per l'algoritmo implementato dalla generazione di URL. In pratica, esempi più complessi di generazione url usano controller o Razor pagine. Per altre informazioni, vedere Routing nei controller .

Il processo di generazione url inizia con una chiamata a LinkGenerator.GetPathByAddress o un metodo simile. Il metodo viene fornito con un indirizzo, un set di valori di route e facoltativamente informazioni sulla richiesta corrente da HttpContext.

Il primo passaggio consiste nell'usare l'indirizzo per risolvere un set di endpoint candidati usando un oggetto IEndpointAddressScheme<TAddress> che corrisponde al tipo di indirizzo.

Dopo che il set di candidati viene trovato dallo schema di indirizzi, gli endpoint vengono ordinati ed elaborati in modo iterativo fino a quando non viene completata un'operazione di generazione url. La generazione di URL non verifica la presenza di ambiguità, il primo risultato restituito è il risultato finale.

Risoluzione dei problemi relativi alla generazione di URL con la registrazione

Il primo passaggio per la risoluzione dei problemi relativi alla generazione di URL consiste nell'impostare il livello di registrazione di Microsoft.AspNetCore.Routing su TRACE. LinkGenerator registra molti dettagli sull'elaborazione che può essere utile per risolvere i problemi.

Per informazioni dettagliate sulla generazione di URL, vedere Informazioni di riferimento sulla generazione di URL.

Indirizzi

Gli indirizzi sono il concetto nella generazione di URL usato per associare una chiamata al generatore di collegamenti a un set di endpoint candidati.

Gli indirizzi sono un concetto estendibile che include due implementazioni per impostazione predefinita:

  • Uso del nome dell'endpoint (string) come indirizzo:
    • Fornisce funzionalità simili al nome della route di MVC.
    • Usa il IEndpointNameMetadata tipo di metadati.
    • Risolve la stringa fornita rispetto ai metadati di tutti gli endpoint registrati.
    • Genera un'eccezione all'avvio se più endpoint usano lo stesso nome.
    • Consigliato per l'uso generico all'esterno di controller e Razor pagine.
  • Uso dei valori di route (RouteValuesAddress) come indirizzo:
    • Fornisce funzionalità simili ai controller e Razor alla generazione di URL legacy di Pages.
    • Molto complesso da estendere ed eseguire il debug.
    • Fornisce l'implementazione usata da IUrlHelper, helper tag, helper HTML, risultati azione e così via.

Il ruolo dello schema di indirizzi consiste nell'eseguire l'associazione tra l'indirizzo e gli endpoint corrispondenti in base a criteri arbitrari:

  • Lo schema dei nomi dell'endpoint esegue una ricerca di dizionario di base.
  • Lo schema dei valori di route ha un subset migliore complesso dell'algoritmo set.

Valori di ambiente e valori espliciti

Dalla richiesta corrente, il routing accede ai valori di route della richiesta HttpContext.Request.RouteValuescorrente. I valori associati alla richiesta corrente vengono definiti valori di ambiente. Ai fini della chiarezza, la documentazione fa riferimento ai valori di route passati ai metodi come valori espliciti.

Nell'esempio seguente vengono illustrati i valori di ambiente e i valori espliciti. Fornisce i valori di ambiente della richiesta corrente e dei valori espliciti:

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

Il codice precedente:

Il codice seguente fornisce solo valori espliciti e nessun valore di ambiente:

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

Il metodo precedente restituisce /Home/Subscribe/17

Il codice seguente in WidgetController restituisce /Widget/Subscribe/17:

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

Il codice seguente fornisce il controller dai valori di ambiente nella richiesta corrente e nei valori espliciti:

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

Nel codice precedente:

  • /Gadget/Edit/17 viene restituito.
  • Url ottiene l'oggetto IUrlHelper.
  • Action genera un URL con un percorso assoluto per un metodo di azione. L'URL contiene il nome e route i valori specificatiaction.

Il codice seguente fornisce i valori di ambiente della richiesta corrente e dei valori espliciti:

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

Il codice precedente imposta su url /Edit/17 quando la pagina di modifica Razor contiene la direttiva di pagina seguente:

@page "{id:int}"

Se la pagina Modifica non contiene il "{id:int}" modello di route, url è /Edit?id=17.

Il comportamento di MVC IUrlHelper aggiunge un livello di complessità oltre alle regole descritte di seguito:

  • IUrlHelper fornisce sempre i valori di route della richiesta corrente come valori di ambiente.
  • IUrlHelper.Action copia sempre i valori correnti action e controller di route come valori espliciti, a meno che non venga sottoposto a override dallo sviluppatore.
  • IUrlHelper.Page copia sempre il valore di route corrente page come valore esplicito, a meno che non venga sottoposto a override.
  • IUrlHelper.Page esegue sempre l'override del valore di route corrente handler con null come valori espliciti, a meno che non venga sottoposto a override.

Gli utenti sono spesso sorpresi dai dettagli comportamentali dei valori di ambiente, perché MVC non sembra seguire le proprie regole. Per motivi cronologici e di compatibilità, alcuni valori di route, actionad esempio , controllerpage, e handler hanno un comportamento specifico.

La funzionalità equivalente fornita da LinkGenerator.GetPathByAction e LinkGenerator.GetPathByPage duplica queste anomalie di IUrlHelper per la compatibilità.

Processo di generazione url

Dopo aver trovato il set di endpoint candidati, l'algoritmo di generazione url:

  • Elabora gli endpoint in modo iterativo.
  • Restituisce il primo risultato riuscito.

Il primo passaggio di questo processo è denominato invalidazione del valore di route. L'invalidazione del valore di route è il processo in base al quale il routing decide quali valori di route dai valori di ambiente devono essere usati e quali devono essere ignorati. Ogni valore di ambiente viene considerato e combinato con i valori espliciti o ignorato.

Il modo migliore per considerare il ruolo dei valori di ambiente è che tentano di salvare gli sviluppatori di applicazioni digitando, in alcuni casi comuni. Tradizionalmente, gli scenari in cui i valori di ambiente sono utili sono correlati a MVC:

  • Quando si esegue il collegamento a un'altra azione nello stesso controller, non è necessario specificare il nome del controller.
  • Quando si esegue il collegamento a un altro controller nella stessa area, non è necessario specificare il nome dell'area.
  • Quando si esegue il collegamento allo stesso metodo di azione, non è necessario specificare i valori di route.
  • Quando si esegue il collegamento a un'altra parte dell'app, non si vogliono trasportare valori di route che non hanno alcun significato in tale parte dell'app.

Le chiamate a LinkGenerator o IUrlHelper che restituiscono null sono in genere causate dalla mancata comprensione dell'invalidazione del valore di route. Risolvere i problemi di invalidazione del valore di route specificando in modo esplicito più valori di route per verificare se questo risolve il problema.

L'invalidazione del valore di route si basa sul presupposto che lo schema URL dell'app sia gerarchico, con una gerarchia formata da sinistra a destra. Si consideri il modello {controller}/{action}/{id?} di route del controller di base per avere un'idea intuitiva del funzionamento di questa operazione in pratica. Una modifica a un valore invalida tutti i valori di route visualizzati a destra. Ciò riflette il presupposto sulla gerarchia. Se l'app ha un valore di ambiente per ide l'operazione specifica un valore diverso per :controller

  • id non verrà riutilizzato perché {controller} si trova a sinistra di {id?}.

Alcuni esempi che illustrano questo principio:

  • Se i valori espliciti contengono un valore per id, il valore di ambiente per id viene ignorato. I valori di ambiente per controller e action possono essere usati.
  • Se i valori espliciti contengono un valore per action, qualsiasi valore di ambiente per action viene ignorato. È possibile usare i valori di ambiente per controller . Se il valore esplicito per action è diverso dal valore di ambiente per action, il id valore non verrà usato. Se il valore esplicito per action è uguale al valore di ambiente per action, è possibile usare il id valore .
  • Se i valori espliciti contengono un valore per controller, qualsiasi valore di ambiente per controller viene ignorato. Se il valore esplicito per controller è diverso dal valore di ambiente per controller, i action valori e id non verranno usati. Se il valore esplicito per controller è uguale al valore di ambiente per controller, è possibile usare i action valori e id .

Questo processo è ulteriormente complicato dall'esistenza di route di attributi e route convenzionali dedicate. Route convenzionali del controller, ad {controller}/{action}/{id?} esempio specificare una gerarchia usando i parametri di route. Per route convenzionali dedicate e route di attributi ai controller e Razor alle pagine:

  • Esiste una gerarchia di valori di route.
  • Non vengono visualizzati nel modello.

Per questi casi, la generazione di URL definisce il concetto di valori necessari. Gli endpoint creati dai controller e Razor pagine hanno valori obbligatori specificati che consentono il funzionamento dell'invalidazione del valore di route.

Algoritmo di invalidazione del valore della route in dettaglio:

  • I nomi dei valori obbligatori vengono combinati con i parametri di route, quindi elaborati da sinistra a destra.
  • Per ogni parametro vengono confrontati il valore di ambiente e il valore esplicito:
    • Se il valore di ambiente e il valore esplicito sono uguali, il processo continua.
    • Se il valore di ambiente è presente e il valore esplicito non è , il valore di ambiente viene usato durante la generazione dell'URL.
    • Se il valore di ambiente non è presente e il valore esplicito è , rifiutare il valore di ambiente e tutti i valori di ambiente successivi.
    • Se sono presenti il valore di ambiente e il valore esplicito e i due valori sono diversi, rifiutare il valore di ambiente e tutti i valori di ambiente successivi.

A questo punto, l'operazione di generazione url è pronta per valutare i vincoli di route. Il set di valori accettati viene combinato con i valori predefiniti del parametro, forniti ai vincoli. Se tutti i vincoli vengono passati, l'operazione continua.

Successivamente, i valori accettati possono essere usati per espandere il modello di route. Il modello di route viene elaborato:

  • Da sinistra a destra.
  • Ogni parametro ha il valore accettato sostituito.
  • Con i casi speciali seguenti:
    • Se i valori accettati mancano un valore e il parametro ha un valore predefinito, viene usato il valore predefinito.
    • Se i valori accettati mancano un valore e il parametro è facoltativo, l'elaborazione continua.
    • Se un parametro di route a destra di un parametro facoltativo mancante ha un valore, l'operazione ha esito negativo.
    • I parametri con valori predefiniti contigui e i parametri facoltativi vengono compressi laddove possibile.

I valori specificati in modo esplicito che non corrispondono a un segmento della route vengono aggiunti alla stringa di query. La tabella seguente illustra il risultato ottenuto quando si usa il modello di route {controller}/{action}/{id?}.

Valori di ambiente Valori espliciti Risultato
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", colore = "Rosso" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

Problemi con l'invalidazione del valore di route

Il codice seguente mostra un esempio di schema di generazione url non supportato dal routing:

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

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

Nel codice precedente il parametro di route viene usato per la culture localizzazione. Il desiderio è avere il culture parametro sempre accettato come valore di ambiente. Tuttavia, il culture parametro non viene accettato come valore di ambiente a causa del funzionamento dei valori richiesti:

  • "default" Nel modello di route il culture parametro di route si trova a sinistra di controller, quindi le modifiche apportate a controller non invalidano culture.
  • "blog" Nel modello di route il culture parametro di route viene considerato a destra di controller, che viene visualizzato nei valori necessari.

Analizzare i percorsi URL con LinkParser

La LinkParser classe aggiunge il supporto per l'analisi di un percorso URL in un set di valori di route. Il ParsePathByEndpointName metodo accetta un nome di endpoint e un percorso URL e restituisce un set di valori di route estratti dal percorso URL.

Nel controller di esempio seguente, l'azione GetProduct usa un modello di route di api/Products/{id} e ha un Name valore di GetProduct:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

Nella stessa classe controller, l'azione AddRelatedProduct prevede un percorso URL, pathToRelatedProduct, che può essere fornito come parametro di stringa di query:

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

Nell'esempio precedente l'azione AddRelatedProduct estrae il valore di id route dal percorso URL. Ad esempio, con un percorso URL di /api/Products/1, il relatedProductId valore è impostato su 1. Questo approccio consente ai client dell'API di usare i percorsi URL quando si fa riferimento alle risorse, senza richiedere la conoscenza della struttura di tale URL.

Configurare i metadati dell'endpoint

I collegamenti seguenti forniscono informazioni su come configurare i metadati dell'endpoint:

Corrispondenza dell'host nelle route con RequireHost

RequireHost applica un vincolo alla route che richiede l'host specificato. Il RequireHost parametro o [Host] può essere:

  • Host: www.domain.com, corrisponde www.domain.com a qualsiasi porta.
  • Host con carattere jolly: *.domain.com, corrisponde www.domain.coma , subdomain.domain.como www.subdomain.domain.com su qualsiasi porta.
  • Porta: *:5000, corrisponde alla porta 5000 con qualsiasi host.
  • Host e porta: www.domain.com:5000 o *.domain.com:5000, corrisponde all'host e alla porta.

È possibile specificare più parametri usando RequireHost o [Host]. Il vincolo corrisponde agli host validi per uno dei parametri. Ad esempio, [Host("domain.com", "*.domain.com")] corrisponde a domain.com, www.domain.come subdomain.domain.com.

Il codice seguente usa RequireHost per richiedere l'host specificato nella route:

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

Il codice seguente usa l'attributo [Host] nel controller per richiedere uno degli host specificati:

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

Quando l'attributo [Host] viene applicato sia al controller che al metodo di azione:

  • Viene usato l'attributo sull'azione.
  • L'attributo controller viene ignorato.

Indicazioni sulle prestazioni per il routing

Quando un'app presenta problemi di prestazioni, il routing viene spesso sospettato come problema. Il routing è sospetto che framework come controller e Razor pagine segnalano la quantità di tempo impiegato all'interno del framework nei messaggi di registrazione. Quando si verifica una differenza significativa tra il tempo segnalato dai controller e il tempo totale della richiesta:

  • Gli sviluppatori eliminano il codice dell'app come origine del problema.
  • È comune presupporre che il routing sia la causa.

Il routing viene testato usando migliaia di endpoint. È improbabile che un'app tipica incontri un problema di prestazioni semplicemente essendo troppo grande. La causa radice più comune delle prestazioni di routing lente è in genere un middleware personalizzato con comportamento non valido.

Questo esempio di codice seguente illustra una tecnica di base per restringere l'origine del ritardo:

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

Routing temporizzato:

  • Interleave ogni middleware con una copia del middleware di intervallo illustrato nel codice precedente.
  • Aggiungere un identificatore univoco per correlare i dati di intervallo con il codice.

Si tratta di un modo di base per limitare il ritardo quando è significativo, ad esempio, più di 10ms. Sottraendo Time 2 dai Time 1 report il tempo trascorso all'interno del UseRouting middleware.

Il codice seguente usa un approccio più compatto al codice di intervallo precedente:

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

Funzionalità di routing potenzialmente costose

L'elenco seguente fornisce alcune informazioni dettagliate sulle funzionalità di routing relativamente costose rispetto ai modelli di route di base:

  • Espressioni regolari: è possibile scrivere espressioni regolari complesse o con tempi di esecuzione lunghi con una piccola quantità di input.
  • Segmenti complessi ({x}-{y}-{z}):
    • Sono notevolmente più costosi rispetto all'analisi di un segmento di percorso URL normale.
    • Comporta l'allocazione di molte più sottostringhe.
  • Accesso ai dati sincrono: molte app complesse hanno accesso al database come parte del routing. Usare punti di estendibilità come MatcherPolicy e EndpointSelectorContext, che sono asincroni.

Linee guida per tabelle di route di grandi dimensioni

Per impostazione predefinita, ASP.NET Core usa un algoritmo di routing che scambia la memoria per il tempo della CPU. Questo ha l'effetto interessante che il tempo di corrispondenza della route dipende solo dalla lunghezza del percorso di corrispondenza e non dal numero di route. Tuttavia, questo approccio può essere potenzialmente problematico in alcuni casi, quando l'app ha un numero elevato di route (nelle migliaia) e c'è una quantità elevata di prefissi variabili nelle route. Ad esempio, se le route hanno parametri nei segmenti iniziali della route, ad {parameter}/some/literalesempio .

È improbabile che un'app si verifichi in una situazione in cui si tratta di un problema a meno che:

  • L'app usa questo modello per un numero elevato di route.
  • Nell'app è presente un numero elevato di route.

Come determinare se un'app è in esecuzione nel problema della tabella di route di grandi dimensioni

  • Esistono due sintomi da cercare:
    • L'app è lenta per l'avvio nella prima richiesta.
      • Si noti che questa operazione è obbligatoria ma non sufficiente. Ci sono molti altri problemi non di route che possono causare un rallentamento dell'avvio dell'app. Verificare la condizione seguente per determinare con precisione che l'app si trova in questa situazione.
    • L'app usa molta memoria durante l'avvio e un dump della memoria mostra un numero elevato di Microsoft.AspNetCore.Routing.Matching.DfaNode istanze.

Come risolvere questo problema

Esistono diverse tecniche e ottimizzazioni che possono essere applicate alle route che miglioreranno in gran parte questo scenario:

  • Applicare vincoli di route ai parametri, ad esempio {parameter:int}, , {parameter:guid}{parameter:regex(\\d+)}e così via, laddove possibile.
    • Ciò consente all'algoritmo di routing di ottimizzare internamente le strutture usate per la corrispondenza e ridurre drasticamente la memoria usata.
    • Nella stragrande maggioranza dei casi questo sarà sufficiente per tornare a un comportamento accettabile.
  • Modificare le route per spostare i parametri in segmenti successivi nel modello.
    • In questo modo si riduce il numero di possibili "percorsi" in modo che corrispondano a un endpoint in base a un percorso.
  • Usare una route dinamica ed eseguire il mapping a un controller/pagina in modo dinamico.
    • A tale scopo, è possibile usare MapDynamicControllerRoute e MapDynamicPageRoute.

Linee guida per gli autori di librerie

Questa sezione contiene indicazioni per gli autori di librerie che si basano sul routing. Questi dettagli sono progettati per garantire che gli sviluppatori di app abbiano un'esperienza ottimale usando librerie e framework che estendono il routing.

Definire gli endpoint

Per creare un framework che usa il routing per la corrispondenza degli URL, iniziare definendo un'esperienza utente basata su UseEndpoints.

Do build on top of IEndpointRouteBuilder. In questo modo gli utenti possono comporre il framework con altre funzionalità di ASP.NET Core senza confusione. Ogni modello ASP.NET Core include il routing. Si supponga che il routing sia presente e familiare per gli utenti.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

DO restituisce un tipo di cemento sigillato da una chiamata a MapMyFramework(...) che implementa IEndpointConventionBuilder. La maggior parte dei metodi del framework Map... segue questo modello. Interfaccia IEndpointConventionBuilder :

  • Consente la composizione dei metadati.
  • È destinato a un'ampia gamma di metodi di estensione.

La dichiarazione del proprio tipo consente di aggiungere funzionalità specifiche del framework al generatore. È possibile eseguire il wrapping di un generatore dichiarato dal framework e inoltrare le chiamate.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

PRENDERE IN CONSIDERAZIONE la scrittura di un proprio EndpointDataSourceoggetto . EndpointDataSource è la primitiva di basso livello per dichiarare e aggiornare una raccolta di endpoint. EndpointDataSource è un'API potente usata dai controller e Razor dalle pagine.

I test di routing hanno un esempio di base di un'origine dati non aggiornata.

NON tentare di registrare un oggetto EndpointDataSource per impostazione predefinita. Richiedere agli utenti di registrare il framework in UseEndpoints. La filosofia del routing è che nulla è incluso per impostazione predefinita e che UseEndpoints è la posizione in cui registrare gli endpoint.

Creazione di middleware integrato nel routing

CONSIDERARE la definizione dei tipi di metadati come interfaccia.

DO consente di usare i tipi di metadati come attributo per classi e metodi.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Framework come controller e Razor pagine supportano l'applicazione di attributi di metadati a tipi e metodi. Se dichiari i tipi di metadati:

  • Renderli accessibili come attributi.
  • La maggior parte degli utenti ha familiarità con l'applicazione degli attributi.

La dichiarazione di un tipo di metadati come interfaccia aggiunge un altro livello di flessibilità:

  • Le interfacce sono componibili.
  • Gli sviluppatori possono dichiarare i propri tipi che combinano più criteri.

FARE in modo che sia possibile eseguire l'override dei metadati, come illustrato nell'esempio seguente:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Il modo migliore per seguire queste linee guida consiste nell'evitare di definire i metadati del marcatore:

  • Non cercare solo la presenza di un tipo di metadati.
  • Definire una proprietà sui metadati e controllare la proprietà .

La raccolta di metadati viene ordinata e supporta l'override in base alla priorità. Nel caso dei controller, i metadati nel metodo di azione sono più specifici.

FARE in modo che il middleware sia utile con e senza routing:

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

Come esempio di questa linea guida, prendere in considerazione il UseAuthorization middleware. Il middleware di autorizzazione consente di passare un criterio di fallback. I criteri di fallback, se specificati, si applicano a entrambi:

  • Endpoint senza criteri specificati.
  • Richieste che non corrispondono a un endpoint.

In questo modo il middleware di autorizzazione risulta utile al di fuori del contesto del routing. Il middleware di autorizzazione può essere usato per la programmazione middleware tradizionale.

Eseguire il debug della diagnostica

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

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

Risorse aggiuntive

Il routing è responsabile della corrispondenza delle richieste HTTP in ingresso e dell'invio di tali richieste agli endpoint eseguibili dell'app. Gli endpoint sono le unità di misura dell'app del codice di gestione delle richieste eseguibili. Gli endpoint vengono definiti nell'app e configurati all'avvio dell'app. Il processo di corrispondenza dell'endpoint può estrarre i valori dall'URL della richiesta e fornire tali valori per l'elaborazione della richiesta. Usando le informazioni sull'endpoint dall'app, il routing è anche in grado di generare URL che eseguono il mapping agli endpoint.

Le app possono configurare il routing usando:

Questo documento illustra i dettagli di basso livello del routing ASP.NET Core. Per informazioni sulla configurazione del routing:

Il sistema di routing degli endpoint descritto in questo documento si applica a ASP.NET Core 3.0 e versioni successive. Per informazioni sul sistema di routing precedente basato su IRouter, selezionare la versione ASP.NET Core 2.1 usando uno degli approcci seguenti:

  • Selettore di versione per una versione precedente.
  • Selezionare ASP.NET routing core 2.1.

Visualizzare o scaricare il codice di esempio (procedura per il download)

Gli esempi di download per questo documento sono abilitati da una classe specifica Startup . Per eseguire un esempio specifico, modificare Program.cs per chiamare la classe desiderata Startup .

Nozioni fondamentali sul routing

Tutti i modelli di base ASP.NET includono il routing nel codice generato. Il routing viene registrato nella pipeline middleware in Startup.Configure.

Il codice seguente illustra un esempio di routing di base:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

Il routing usa una coppia di middleware, registrati da UseRouting e UseEndpoints:

  • UseRouting aggiunge la corrispondenza della route alla pipeline middleware. Questo middleware esamina il set di endpoint definiti nell'app e seleziona la corrispondenza migliore in base alla richiesta.
  • UseEndpoints aggiunge l'esecuzione dell'endpoint alla pipeline middleware. Esegue il delegato associato all'endpoint selezionato.

L'esempio precedente include una singola route all'endpoint di codice usando il metodo MapGet :

  • Quando viene inviata una richiesta HTTP GET all'URL /radice :
    • Il delegato di richiesta visualizzato viene eseguito.
    • Hello World! viene scritto nella risposta HTTP. Per impostazione predefinita, l'URL / radice è https://localhost:5001/.
  • Se il metodo di richiesta non GET è o l'URL radice non /è , non viene restituita alcuna corrispondenza di route e viene restituito un HTTP 404.

Endpoint

Il MapGet metodo viene usato per definire un endpoint. Un endpoint è un elemento che può essere:

  • Selezionato, associando l'URL e il metodo HTTP.
  • Eseguito eseguendo il delegato .

Gli endpoint che possono essere confrontati ed eseguiti dall'app vengono configurati in UseEndpoints. Ad esempio, MapGet, MapPoste metodi simili connettono i delegati di richiesta al sistema di routing. È possibile usare metodi aggiuntivi per connettere ASP.NET funzionalità del framework Core al sistema di routing:

L'esempio seguente mostra il routing con un modello di route più sofisticato:

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/hello/{name:alpha}", async context =>
    {
        var name = context.Request.RouteValues["name"];
        await context.Response.WriteAsync($"Hello {name}!");
    });
});

La stringa /hello/{name:alpha} è un modello di route. Viene usato per configurare la corrispondenza dell'endpoint. In questo caso, il modello corrisponde a:

  • UN URL come /hello/Ryan
  • Qualsiasi percorso URL che inizia con /hello/ seguito da una sequenza di caratteri alfabetici. :alpha applica un vincolo di route che corrisponde solo ai caratteri alfabetici. I vincoli di route vengono illustrati più avanti in questo documento.

Secondo segmento del percorso URL, {name:alpha}:

Il sistema di routing degli endpoint descritto in questo documento è nuovo a partire da ASP.NET Core 3.0. Tuttavia, tutte le versioni di ASP.NET Core supportano lo stesso set di funzionalità del modello di route e vincoli di route.

L'esempio seguente mostra il routing con controlli di integrità e autorizzazione:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // Matches request to an endpoint.
    app.UseRouting();

    // Endpoint aware middleware. 
    // Middleware can use metadata from the matched endpoint.
    app.UseAuthentication();
    app.UseAuthorization();

    // Execute the matched endpoint.
    app.UseEndpoints(endpoints =>
    {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();

        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

Per visualizzare i commenti del codice tradotti in lingue diverse dall'inglese, segnalarlo in questo problema di discussione su GitHub.

L'esempio precedente illustra come:

  • Il middleware di autorizzazione può essere usato con il routing.
  • Gli endpoint possono essere usati per configurare il comportamento di autorizzazione.

La MapHealthChecks chiamata aggiunge un endpoint di controllo integrità. Il concatenamento RequireAuthorization a questa chiamata collega un criterio di autorizzazione all'endpoint.

La chiamata UseAuthentication e UseAuthorization aggiunge il middleware di autenticazione e autorizzazione. Questi middleware vengono posizionati tra UseRouting e UseEndpoints in modo che possano:

  • Vedere quale endpoint è stato selezionato da UseRouting.
  • Applicare un criterio di autorizzazione prima UseEndpoints dell'invio all'endpoint.

Metadati dell'endpoint

Nell'esempio precedente sono presenti due endpoint, ma solo l'endpoint di controllo integrità ha un criterio di autorizzazione associato. Se la richiesta corrisponde all'endpoint del controllo integrità, /healthzviene eseguito un controllo di autorizzazione. Ciò dimostra che gli endpoint possono avere dati aggiuntivi collegati. Questi dati aggiuntivi sono denominati metadati dell'endpoint:

  • I metadati possono essere elaborati tramite middleware compatibile con il routing.
  • I metadati possono essere di qualsiasi tipo .NET.

Concetti relativi al routing

Il sistema di routing si basa sulla pipeline middleware aggiungendo il concetto avanzato di endpoint . Gli endpoint rappresentano unità di funzionalità dell'app distinte l'una dall'altra in termini di routing, autorizzazione e qualsiasi numero di sistemi ASP.NET Core.

definizione dell'endpoint principale ASP.NET

Un endpoint ASP.NET Core è:

Il codice seguente illustra come recuperare ed esaminare l'endpoint corrispondente alla richiesta corrente:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.Use(next => context =>
    {
        var endpoint = context.GetEndpoint();
        if (endpoint is null)
        {
            return Task.CompletedTask;
        }
        
        Console.WriteLine($"Endpoint: {endpoint.DisplayName}");

        if (endpoint is RouteEndpoint routeEndpoint)
        {
            Console.WriteLine("Endpoint has route pattern: " +
                routeEndpoint.RoutePattern.RawText);
        }

        foreach (var metadata in endpoint.Metadata)
        {
            Console.WriteLine($"Endpoint has metadata: {metadata}");
        }

        return Task.CompletedTask;
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

L'endpoint, se selezionato, può essere recuperato da HttpContext. È possibile controllarne le proprietà. Gli oggetti endpoint non sono modificabili e non possono essere modificati dopo la creazione. Il tipo di endpoint più comune è .RouteEndpoint RouteEndpoint include informazioni che consentono di selezionarla dal sistema di routing.

Nel codice precedente, app. Usa configura un middleware inline.

Il codice seguente mostra che, a seconda della posizione in cui app.Use viene chiamato nella pipeline, potrebbe non esserci un endpoint:

// Location 1: before routing runs, endpoint is always null here
app.Use(next => context =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match
app.Use(next => context =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

app.UseEndpoints(endpoints =>
{
    // Location 3: runs when this endpoint matches
    endpoints.MapGet("/", context =>
    {
        Console.WriteLine(
            $"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
        return Task.CompletedTask;
    }).WithDisplayName("Hello");
});

// Location 4: runs after UseEndpoints - will only run if there was no match
app.Use(next => context =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

In questo esempio precedente vengono aggiunte Console.WriteLine istruzioni che indicano se è stato selezionato o meno un endpoint. Per maggiore chiarezza, l'esempio assegna un nome visualizzato all'endpoint fornito / .

Esecuzione di questo codice con un URL di / visualizzazione:

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

L'esecuzione di questo codice con qualsiasi altro URL viene visualizzata:

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Questo output dimostra che:

  • L'endpoint è sempre Null prima UseRouting di essere chiamato.
  • Se viene trovata una corrispondenza, l'endpoint non è Null tra UseRouting e UseEndpoints.
  • Il UseEndpoints middleware è terminale quando viene trovata una corrispondenza. Il middleware del terminale viene definito più avanti in questo documento.
  • Middleware dopo UseEndpoints l'esecuzione solo quando non viene trovata alcuna corrispondenza.

Il UseRouting middleware usa il metodo SetEndpoint per collegare l'endpoint al contesto corrente. È possibile sostituire il middleware con la UseRouting logica personalizzata e ottenere comunque i vantaggi dell'uso degli endpoint. Gli endpoint sono primitivi di basso livello come middleware e non sono associati all'implementazione del routing. La maggior parte delle app non deve sostituire UseRouting con la logica personalizzata.

Il UseEndpoints middleware è progettato per essere usato in parallelo con il UseRouting middleware. La logica di base per eseguire un endpoint non è complicata. Usare GetEndpoint per recuperare l'endpoint e quindi richiamarne la RequestDelegate proprietà.

Il codice seguente illustra come il middleware può influenzare o reagire al routing:

public class IntegratedMiddlewareStartup
{ 
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        // Location 1: Before routing runs. Can influence request before routing runs.
        app.UseHttpMethodOverride();

        app.UseRouting();

        // Location 2: After routing runs. Middleware can match based on metadata.
        app.Use(next => context =>
        {
            var endpoint = context.GetEndpoint();
            if (endpoint?.Metadata.GetMetadata<AuditPolicyAttribute>()?.NeedsAudit
                                                                            == true)
            {
                Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
            }

            return next(context);
        });

        app.UseEndpoints(endpoints =>
        {         
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Hello world!");
            });

            // Using metadata to configure the audit policy.
            endpoints.MapGet("/sensitive", async context =>
            {
                await context.Response.WriteAsync("sensitive data");
            })
            .WithMetadata(new AuditPolicyAttribute(needsAudit: true));
        });

    } 
}

public class AuditPolicyAttribute : Attribute
{
    public AuditPolicyAttribute(bool needsAudit)
    {
        NeedsAudit = needsAudit;
    }

    public bool NeedsAudit { get; }
}

L'esempio precedente illustra due concetti importanti:

  • Il middleware può essere eseguito prima UseRouting di modificare i dati su cui opera il routing.
  • Il middleware può essere eseguito tra UseRouting e UseEndpoints per elaborare i risultati del routing prima dell'esecuzione dell'endpoint.
    • Middleware eseguito tra UseRouting e UseEndpoints:
      • In genere controlla i metadati per comprendere gli endpoint.
      • Spesso prende decisioni di sicurezza, come fatto da UseAuthorization e UseCors.
    • La combinazione di middleware e metadati consente di configurare i criteri per endpoint.

Il codice precedente mostra un esempio di middleware personalizzato che supporta i criteri per endpoint. Il middleware scrive un log di controllo dell'accesso ai dati sensibili nella console. Il middleware può essere configurato per controllare un endpoint con i AuditPolicyAttribute metadati. Questo esempio illustra un modello di consenso esplicito in cui vengono controllati solo gli endpoint contrassegnati come sensibili. È possibile definire questa logica inversa, controllando tutto ciò che non è contrassegnato come sicuro, ad esempio. Il sistema di metadati dell'endpoint è flessibile. Questa logica può essere progettata in qualsiasi modo adatto al caso d'uso.

Il codice di esempio precedente è progettato per illustrare i concetti di base degli endpoint. L'esempio non è destinato all'uso in produzione. Una versione più completa di un middleware del log di controllo è la seguente:

  • Accedere a un file o a un database.
  • Includere dettagli, ad esempio l'utente, l'indirizzo IP, il nome dell'endpoint sensibile e altro ancora.

I metadati dei criteri AuditPolicyAttribute di controllo vengono definiti come un oggetto per un Attribute uso più semplice con framework basati su classi, ad esempio controller e SignalR. Quando si usa la route al codice:

  • I metadati vengono associati a un'API del generatore.
  • I framework basati su classi includono tutti gli attributi nel metodo e nella classe corrispondenti durante la creazione di endpoint.

Le procedure consigliate per i tipi di metadati sono di definirle come interfacce o attributi. Le interfacce e gli attributi consentono il riutilizzo del codice. Il sistema di metadati è flessibile e non impone alcuna limitazione.

Confronto tra un middleware terminale e il routing

L'esempio di codice seguente contrasta l'uso del middleware con l'uso del routing:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // Approach 1: Writing a terminal middleware.
    app.Use(next => async context =>
    {
        if (context.Request.Path == "/")
        {
            await context.Response.WriteAsync("Hello terminal middleware!");
            return;
        }

        await next(context);
    });

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        // Approach 2: Using routing.
        endpoints.MapGet("/Movie", async context =>
        {
            await context.Response.WriteAsync("Hello routing!");
        });
    });
}

Lo stile del middleware visualizzato con Approach 1: è il middleware del terminale. Si chiama middleware del terminale perché esegue un'operazione corrispondente:

  • L'operazione di corrispondenza nell'esempio precedente è Path == "/" relativa al middleware e Path == "/Movie" al routing.
  • Quando una corrispondenza ha esito positivo, esegue alcune funzionalità e restituisce, anziché richiamare il next middleware.

Si tratta di un middleware terminale perché termina la ricerca, esegue alcune funzionalità e quindi restituisce.

Confronto tra middleware e routing del terminale:

  • Entrambi gli approcci consentono di terminare la pipeline di elaborazione:
    • Il middleware termina la pipeline restituendo anziché richiamando next.
    • Gli endpoint sono sempre terminale.
  • Il middleware del terminale consente di posizionare il middleware in una posizione arbitraria nella pipeline:
    • Gli endpoint sono eseguiti nella posizione di UseEndpoints.
  • Il middleware del terminale consente al codice arbitrario di determinare quando il middleware corrisponde:
    • Il codice personalizzato di corrispondenza della route può essere dettagliato e difficile da scrivere correttamente.
    • Il routing offre soluzioni semplici per le app tipiche. La maggior parte delle app non richiede codice di corrispondenza di route personalizzato.
  • Interfaccia degli endpoint con middleware, UseAuthorization ad esempio e UseCors.
    • L'uso di un middleware del terminale con UseAuthorization o UseCors richiede l'interfaccia manuale con il sistema di autorizzazione.

Un endpoint definisce entrambi:

  • Delegato per elaborare le richieste.
  • Raccolta di metadati arbitrari. I metadati vengono usati per implementare problematiche trasversali in base ai criteri e alla configurazione collegati a ogni endpoint.

Il middleware del terminale può essere uno strumento efficace, ma può richiedere:

  • Quantità significativa di codifica e test.
  • Integrazione manuale con altri sistemi per ottenere il livello di flessibilità desiderato.

Prendere in considerazione l'integrazione con il routing prima di scrivere un middleware del terminale.

Il middleware del terminale esistente che si integra con Map o MapWhen in genere può essere trasformato in un endpoint compatibile con il routing. MapHealthChecks illustra il modello per router-ware:

Il codice seguente illustra l'uso di MapHealthChecks:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // Matches request to an endpoint.
    app.UseRouting();

    // Endpoint aware middleware. 
    // Middleware can use metadata from the matched endpoint.
    app.UseAuthentication();
    app.UseAuthorization();

    // Execute the matched endpoint.
    app.UseEndpoints(endpoints =>
    {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();

        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

L'esempio precedente mostra perché la restituzione dell'oggetto builder è importante. La restituzione dell'oggetto builder consente allo sviluppatore di app di configurare criteri come l'autorizzazione per l'endpoint. In questo esempio, il middleware dei controlli di integrità non ha alcuna integrazione diretta con il sistema di autorizzazione.

Il sistema di metadati è stato creato in risposta ai problemi riscontrati dagli autori dell'estendibilità usando il middleware del terminale. È problematico per ogni middleware implementare la propria integrazione con il sistema di autorizzazione.

Corrispondenza URL

  • Processo in base al quale il routing corrisponde a una richiesta in ingresso a un endpoint.
  • Si basa sui dati nel percorso e nelle intestazioni URL.
  • Può essere esteso per prendere in considerazione tutti i dati nella richiesta.

Quando viene eseguito un middleware di routing, imposta un valore Endpoint e indirizza i valori a una funzionalità di richiesta nella HttpContext richiesta corrente:

  • La chiamata a HttpContext.GetEndpoint ottiene l'endpoint.
  • HttpRequest.RouteValues ottiene la raccolta dei valori di route.

Il middleware viene eseguito dopo il middleware di routing può esaminare l'endpoint ed eseguire azioni. Ad esempio, un middleware di autorizzazione può interrogare la raccolta di metadati dell'endpoint per un criterio di autorizzazione. Dopo l'esecuzione di tutto il middleware nella pipeline di elaborazione della richiesta, viene richiamato il delegato dell'endpoint selezionato.

Il sistema di routing nell'endpoint di routing è responsabile di tutte le decisioni relative all'invio. Poiché il middleware applica i criteri in base all'endpoint selezionato, è importante:

  • Qualsiasi decisione che può influire sull'invio o sull'applicazione dei criteri di sicurezza viene effettuata all'interno del sistema di routing.

Avviso

Per garantire la compatibilità con le versioni precedenti, quando viene eseguito un delegato dell'endpoint Controller o Razor Pages, le proprietà di RouteContext.RouteData vengono impostate su valori appropriati in base all'elaborazione della richiesta eseguita finora.

Il RouteContext tipo verrà contrassegnato come obsoleto in una versione futura:

  • Eseguire la migrazione RouteData.Values a HttpRequest.RouteValues.
  • Eseguire la migrazione RouteData.DataTokens per recuperare IDataTokensMetadata dai metadati dell'endpoint.

La corrispondenza degli URL opera in un set configurabile di fasi. In ogni fase, l'output è un set di corrispondenze. Il set di corrispondenze può essere limitato ulteriormente dalla fase successiva. L'implementazione del routing non garantisce un ordine di elaborazione per gli endpoint corrispondenti. Tutte le possibili corrispondenze vengono elaborate contemporaneamente. Le fasi di corrispondenza dell'URL si verificano nell'ordine seguente. ASP.NET Core:

  1. Elabora il percorso URL rispetto al set di endpoint e ai relativi modelli di route, raccogliendo tutte le corrispondenze.
  2. Accetta l'elenco precedente e rimuove le corrispondenze che hanno esito negativo con vincoli di route applicati.
  3. Accetta l'elenco precedente e rimuove le corrispondenze che non superano il set di istanze matcherPolicy.
  4. Usa EndpointSelector per prendere una decisione finale dall'elenco precedente.

L'elenco degli endpoint è prioritario in base a:

Tutti gli endpoint corrispondenti vengono elaborati in ogni fase fino a quando non viene raggiunto .EndpointSelector è EndpointSelector la fase finale. Sceglie l'endpoint con priorità più alta tra le corrispondenze come corrispondenza migliore. Se sono presenti altre corrispondenze con la stessa priorità della corrispondenza migliore, viene generata un'eccezione di corrispondenza ambigua.

La precedenza della route viene calcolata in base a un modello di route più specifico a cui viene assegnata una priorità più alta. Si considerino ad esempio i modelli /hello e /{message}:

  • Entrambi corrispondono al percorso /helloURL .
  • /hello è più specifico e quindi priorità più alta.

In generale, la precedenza di route fa un buon lavoro per scegliere la corrispondenza migliore per i tipi di schemi URL usati in pratica. Usare Order solo quando necessario per evitare ambiguità.

A causa dei tipi di estendibilità forniti dal routing, non è possibile che il sistema di routing calcoli in anticipo le route ambigue. Si consideri un esempio, ad esempio i modelli /{message:alpha} di route e /{message:int}:

  • Il alpha vincolo corrisponde solo ai caratteri alfabetici.
  • Il int vincolo corrisponde solo ai numeri.
  • Questi modelli hanno la stessa precedenza di route, ma non esiste un singolo URL che corrispondono entrambi.
  • Se il sistema di routing ha segnalato un errore di ambiguità all'avvio, blocca questo caso d'uso valido.

Avviso

L'ordine delle operazioni all'interno UseEndpoints non influisce sul comportamento del routing, con un'eccezione. MapControllerRoute e MapAreaRoute assegnano automaticamente un valore di ordine ai relativi endpoint in base all'ordine in cui vengono richiamati. Questo simula il comportamento a lungo termine dei controller senza che il sistema di routing fornisca le stesse garanzie delle implementazioni di routing meno recenti.

Nell'implementazione legacy del routing, è possibile implementare l'estendibilità del routing che ha una dipendenza dall'ordine in cui vengono elaborate le route. Routing degli endpoint in ASP.NET Core 3.0 e versioni successive:

  • Non ha un concetto di route.
  • Non fornisce garanzie di ordinamento. Tutti gli endpoint vengono elaborati contemporaneamente.

Precedenza del modello di route e ordine di selezione dell'endpoint

La precedenza del modello di route è un sistema che assegna a ogni modello di route un valore in base al modo in cui è specifico. Precedenza del modello di route:

  • Evita la necessità di modificare l'ordine degli endpoint nei casi comuni.
  • Tenta di corrispondere alle aspettative di buon senso del comportamento di routing.

Si considerino ad esempio modelli /Products/List e /Products/{id}. Sarebbe ragionevole presupporre che /Products/List sia una corrispondenza migliore rispetto /Products/{id} al percorso /Products/ListURL . Ciò funziona perché il segmento letterale /List viene considerato avere una precedenza migliore rispetto al segmento di /{id}parametro .

I dettagli sul funzionamento della precedenza sono associati alla definizione dei modelli di route:

  • I modelli con più segmenti sono considerati più specifici.
  • Un segmento con testo letterale è considerato più specifico di un segmento di parametro.
  • Un segmento di parametro con un vincolo viene considerato più specifico di uno senza.
  • Un segmento complesso viene considerato specifico come segmento di parametro con un vincolo.
  • I parametri catch-all sono i meno specifici. Per informazioni importanti sulle route catch-all, vedere catch-all nel riferimento al modello di route.

Per informazioni sui valori esatti, vedere il codice sorgente in GitHub .

Concetti relativi alla generazione di URL

Generazione url:

  • Processo in base al quale il routing può creare un percorso URL basato su un set di valori di route.
  • Consente una separazione logica tra gli endpoint e gli URL che vi accedono.

Il routing degli endpoint include l'API LinkGenerator . LinkGeneratorè un servizio singleton disponibile dall'inserimento delle dipendenze. L'API LinkGenerator può essere usata al di fuori del contesto di una richiesta in esecuzione. Mvc.IUrlHelper e scenari che si basano su IUrlHelper, ad esempio helper tag, helper HTML e risultati azione, usano l'API LinkGenerator internamente per fornire funzionalità di generazione di collegamenti.

Il generatore di collegamenti si basa sui concetti di indirizzo e di schemi di indirizzi. Lo schema di indirizzi consente di determinare gli endpoint che devono essere considerati per la generazione dei collegamenti. Ad esempio, gli scenari di nome della route e valori di route con cui molti utenti hanno familiarità con i controller e Razor le pagine vengono implementati come schema di indirizzi.

Il generatore di collegamenti può collegarsi ai controller e Razor alle pagine tramite i metodi di estensione seguenti:

Gli overload di questi metodi accettano argomenti che includono .HttpContext Questi metodi sono funzionalmente equivalenti a Url.Action e Url.Page, ma offrono flessibilità e opzioni aggiuntive.

I GetPath* metodi sono più simili a Url.Action e Url.Page, in quanto generano un URI contenente un percorso assoluto. I metodi GetUri* generano sempre un URI assoluto contenente uno schema e un host. I metodi che accettano HttpContext generano un URI nel contesto della richiesta in esecuzione. I valori di route di ambiente , il percorso di base URL, lo schema e l'host della richiesta in esecuzione vengono usati a meno che non venga sottoposto a override.

LinkGenerator viene chiamato con un indirizzo. La generazione di un URI viene eseguita in due passaggi:

  1. Un indirizzo viene associato a un elenco di endpoint che corrispondono all'indirizzo.
  2. RoutePattern di ogni endpoint viene valutato fino a quando non viene individuato un formato di route che corrisponde ai valori specificati. L'output risultante viene unito alle altre parti dell'URI specificate nel generatore di collegamenti e restituito.

I metodi forniti da LinkGenerator supportano le funzionalità di generazione di collegamenti standard per tutti i tipi di indirizzi. Il modo più pratico per usare il generatore di collegamenti è tramite metodi di estensione che eseguono operazioni per un tipo di indirizzo specifico:

Metodo di estensione Descrizione
GetPathByAddress Genera un URI con un percorso assoluto in base ai valori specificati.
GetUriByAddress Genera un URI assoluto in base ai valori specificati.

Avviso

Prestare attenzione alle implicazioni seguenti della chiamata ai metodi LinkGenerator:

  • Usare i metodi di estensione GetUri* con cautela in una configurazione di app che non convalida l'intestazione Host delle richieste in ingresso. Se l'intestazione delle richieste in ingresso non viene convalidata, l'input Host di richiesta non attendibile può essere inviato al client negli URI di una visualizzazione o di una pagina. È consigliabile che in tutte le app di produzione il server sia configurato per la convalida dell'intestazione Host rispetto ai valori validi noti.

  • Usare LinkGenerator con cautela nel middleware in associazione a Map o MapWhen. Map* modifica il percorso di base della richiesta in esecuzione, che ha effetto sull'output della generazione di collegamenti. Tutte le API LinkGenerator consentono di specificare un percorso di base. Specificare un percorso di base vuoto per annullare l'effetto Map* sulla generazione del collegamento.

Esempio di middleware

Nell'esempio seguente un middleware usa l'API LinkGenerator per creare un collegamento a un metodo di azione che elenca i prodotti dell'archivio. L'uso del generatore di collegamenti inserendolo in una classe e chiamando GenerateLink è disponibile per qualsiasi classe in un'app:

public class ProductsLinkMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsLinkMiddleware(RequestDelegate next, LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        var url = _linkGenerator.GetPathByAction("ListProducts", "Store");

        httpContext.Response.ContentType = "text/plain";

        await httpContext.Response.WriteAsync($"Go to {url} to see our products.");
    }
}

Riferimento per il modello di route

I token all'interno {} definiscono i parametri di route associati se la route corrisponde. È possibile definire più parametri di route in un segmento di route, ma i parametri di route devono essere separati da un valore letterale. Ad esempio, {controller=Home}{action=Index} non è una route valida perché non è presente un valore letterale tra {controller} e {action}. I parametri di route devono avere un nome e possono avere attributi aggiuntivi specificati.

Il testo letterale diverso dai parametri di route, ad esempio {id}, e il separatore di percorso / devono corrispondere al testo nell'URL. La corrispondenza del testo non fa distinzione tra maiuscole e minuscole e si basa sulla rappresentazione decodificata del percorso dell'URL. Per trovare una corrispondenza con un delimitatore { di parametro di route letterale o }, eseguire l'escape del delimitatore ripetendo il carattere. Ad esempio {{ o }}.

Asterisco * o asterisco **doppio:

  • Può essere usato come prefisso per un parametro di route da associare all'URI rest .
  • Vengono chiamati parametri catch-all . Ad esempio, blog/{**slug}:
    • Trova la corrispondenza con qualsiasi URI che inizia con /blog e ha qualsiasi valore dopo di esso.
    • Il valore seguente /blog viene assegnato al valore di route slug .

Avviso

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

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

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

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

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

I parametri catch-all possono anche corrispondere alla stringa vuota.

Il parametro catch-all esegue l'escape dei caratteri appropriati quando viene usata la route per generare un URL, inclusi i caratteri separatori / di percorso. Ad esempio, la route foo/{*path} con i valori di route { path = "my/path" } genera foo/my%2Fpath. Si noti la barra di escape. Per eseguire il round trip dei caratteri di separatore di percorso, usare il prefisso del parametro di route **. La route foo/{**path} con { path = "my/path" } genera foo/my/path.

I modelli di URL che tentano di acquisire un nome file con un'estensione facoltativa hanno considerazioni aggiuntive. Considerare ad esempio il modello files/{filename}.{ext?}. Se esistono i valori per filename e ext, vengono popolati entrambi i valori. Se nell'URL esiste solo un valore per filename , la route corrisponde perché l'elemento finale . è facoltativo. Gli URL seguenti corrispondono a questa route:

  • /files/myFile.txt
  • /files/myFile

I parametri di route possono avere valori predefiniti, definiti specificando il valore predefinito dopo il nome del parametro, separato da un segno di uguale (=). Ad esempio, {controller=Home} definisce Home come valore predefinito per controller. Il valore predefinito viene usato se nell'URL non è presente alcun valore per il parametro. I parametri di route vengono resi facoltativi aggiungendo un punto interrogativo (?) alla fine del nome del parametro. Ad esempio: id?. La differenza tra i valori facoltativi e i parametri di route predefiniti è:

  • Un parametro di route con un valore predefinito produce sempre un valore.
  • Un parametro facoltativo ha un valore solo quando un valore viene fornito dall'URL della richiesta.

I parametri di route possono presentare dei vincoli che devono corrispondere al valore di route associato dall'URL. L'aggiunta : del nome del vincolo e dopo il nome del parametro di route specifica un vincolo inline su un parametro di route. Se il vincolo richiede argomenti, vengono racchiusi tra parentesi (...) dopo il nome del vincolo. È possibile specificare più vincoli inline aggiungendo un altro : nome di vincolo e .

Il nome del vincolo e gli argomenti vengono passati al servizio IInlineConstraintResolver per creare un'istanza di IRouteConstraint da usare nell'elaborazione dell'URL. Il modello di route blog/{article:minlength(10)} specifica ad esempio un vincolo minlength con l'argomento 10. Per altre informazioni sui vincoli di route e per un elenco dei vincoli specificati dal framework, vedere la sezione Riferimento per i vincoli di route.

I parametri di route possono avere anche trasformatori di parametro. I trasformatori di parametro trasformano il valore di un parametro durante la generazione di collegamenti e azioni e pagine corrispondenti agli URL. Analogamente ai vincoli, i trasformatori di parametro possono essere aggiunti inline a un parametro di route aggiungendo un : nome di trasformatore e dopo il nome del parametro di route. Ad esempio, il modello di route blog/{article:slugify} specifica un trasformatore slugify. Per altre informazioni sui trasformatori di parametro, vedere la sezione Riferimento ai trasformatori di parametro.

La tabella seguente illustra modelli di route di esempio e il relativo comportamento:

Modello di route URI corrispondente di esempio L'URI della richiesta
hello /hello Verifica la corrispondenza solo del singolo percorso /hello.
{Page=Home} / Verifica la corrispondenza e imposta Page su Home.
{Page=Home} /Contact Verifica la corrispondenza e imposta Page su Contact.
{controller}/{action}/{id?} /Products/List Esegue il mapping al controller Products e all'azione List.
{controller}/{action}/{id?} /Products/Details/123 Esegue il Products mapping al controller e Details all'azione conid impostato su 123.
{controller=Home}/{action=Index}/{id?} / Esegue il mapping al controller e Index al Home metodo . id viene ignorato.
{controller=Home}/{action=Index}/{id?} /Products Esegue il mapping al controller e Index al Products metodo . id viene ignorato.

L'uso di un modello è in genere l'approccio più semplice al routing. I vincoli e le impostazioni predefinite possono essere specificati anche all'esterno del modello di route.

Segmenti complessi

I segmenti complessi vengono elaborati associando delimitatori letterali da destra a sinistra in modo non greedy . Ad esempio, [Route("/a{b}c{d}")] è un segmento complesso. I segmenti complessi funzionano in modo particolare che devono essere compresi per usarli correttamente. L'esempio in questa sezione illustra perché i segmenti complessi funzionano correttamente solo quando il testo del delimitatore non viene visualizzato all'interno dei valori dei parametri. L'uso di un'espressione regolare e quindi l'estrazione manuale dei valori è necessaria per casi più complessi.

Avviso

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

Si tratta di un riepilogo dei passaggi che il routing esegue con il modello /a{b}c{d} e il percorso /abcdURL . Viene | usato per visualizzare il funzionamento dell'algoritmo:

  • Il primo valore letterale, da destra a sinistra, è c. Quindi /abcd viene eseguita la ricerca da destra e trova /ab|c|d.
  • Tutti gli elementi a destra (d) vengono ora confrontati con il parametro {d}di route .
  • Il valore letterale successivo, da destra a sinistra, è a. Quindi /ab|c|d viene eseguita una ricerca a partire dal punto in cui è stato interrotto, quindi a viene trovato /|a|b|c|d.
  • Il valore a destra (b) viene ora confrontato con il parametro {b}di route .
  • Nessun testo rimanente e nessun modello di route rimanente, quindi si tratta di una corrispondenza.

Di seguito è riportato un esempio di caso negativo usando lo stesso modello /a{b}c{d} e il percorso /aabcdURL . Viene | usato per visualizzare il funzionamento dell'algoritmo. Questo caso non è una corrispondenza, spiegata dallo stesso algoritmo:

  • Il primo valore letterale, da destra a sinistra, è c. Quindi /aabcd viene eseguita la ricerca da destra e trova /aab|c|d.
  • Tutti gli elementi a destra (d) vengono ora confrontati con il parametro {d}di route .
  • Il valore letterale successivo, da destra a sinistra, è a. Quindi /aab|c|d viene eseguita una ricerca a partire dal punto in cui è stato interrotto, quindi a viene trovato /a|a|b|c|d.
  • Il valore a destra (b) viene ora confrontato con il parametro {b}di route .
  • A questo punto è presente testo arimanente, ma l'algoritmo ha esaurito il modello di route da analizzare, quindi non si tratta di una corrispondenza.

Poiché l'algoritmo corrispondente non è greedy:

  • Corrisponde alla quantità minima di testo possibile in ogni passaggio.
  • Ogni caso in cui il valore del delimitatore venga visualizzato all'interno dei valori dei parametri non corrisponde.

Le espressioni regolari offrono un maggiore controllo sul comportamento di corrispondenza.

La corrispondenza Greedy, nota anche come corrispondenza differita, corrisponde alla stringa più grande possibile. Non greedy corrisponde alla stringa più piccola possibile.

Riferimento per i vincoli di route

I vincoli di route vengono eseguiti quando si verifica una corrispondenza nell'URL in ingresso e il percorso URL viene suddiviso in valori di route in formato token. I vincoli di route in genere controllano il valore di route associato tramite il modello di route e prendere una decisione vera o falsa circa se il valore è accettabile. Alcuni vincoli di route usano i dati all'esterno del valore di route per stabilire se la richiesta può essere instradata. Ad esempio, HttpMethodRouteConstraint può accettare o rifiutare una richiesta in base al relativo verbo HTTP. I vincoli vengono usati nelle richieste del routing e nella generazione di collegamenti.

Avviso

Non usare i vincoli per la convalida dell'input. Se i vincoli vengono usati per la convalida dell'input, l'input non valido restituisce una 404 risposta non trovata. L'input non valido deve produrre una 400 richiesta non valida con un messaggio di errore appropriato. I vincoli di route vengono usati per evitare ambiguità tra route simili, non per convalidare gli input per una route specifica.

La tabella seguente illustra i vincoli di route di esempio e il relativo comportamento previsto:

vincolo Esempio Esempi di corrispondenza Note
int {id:int} 123456789, -123456789 Corrisponde a qualsiasi numero intero
bool {active:bool} true, FALSE Corrisponde true a o false. Nessuna distinzione tra maiuscole e minuscole
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Corrisponde a un valore valido DateTime nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
decimal {price:decimal} 49.99, -1,000.01 Corrisponde a un valore valido decimal nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
double {weight:double} 1.234, -1,001.01e8 Corrisponde a un valore valido double nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
float {weight:float} 1.234, -1,001.01e8 Corrisponde a un valore valido float nelle impostazioni cultura invarianti. Vedere l'avviso precedente.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Corrisponde a un valore Guid valido
long {ticks:long} 123456789, -123456789 Corrisponde a un valore long valido
minlength(value) {username:minlength(4)} Rick La stringa deve contenere almeno 4 caratteri
maxlength(value) {filename:maxlength(8)} MyFile La stringa non deve contenere più di 8 caratteri
length(length) {filename:length(12)} somefile.txt La stringa deve contenere esattamente 12 caratteri
length(min,max) {filename:length(8,16)} somefile.txt La stringa deve contenere almeno 8 e non più di 16 caratteri
min(value) {age:min(18)} 19 Il valore intero deve essere almeno 18
max(value) {age:max(120)} 91 Il valore intero non deve essere superiore a 120
range(min,max) {age:range(18,120)} 91 Il valore intero deve essere almeno 18 ma non più di 120
alpha {name:alpha} Rick La stringa deve essere costituita da uno o più caratteri a-z alfabetici e senza distinzione tra maiuscole e minuscole.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 La stringa deve corrispondere all'espressione regolare. Vedere suggerimenti sulla definizione di un'espressione regolare.
required {name:required} Rick Usato per imporre che un valore diverso da un parametro sia presente durante la generazione dell'URL

Avviso

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

Più vincoli delimitati da due punti possono essere applicati a un singolo parametro. Ad esempio il vincolo seguente limita un parametro a un valore intero maggiore o uguale a 1:

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Avviso

I vincoli di route che verificano l'URL e vengono convertiti in un tipo CLR usano sempre le impostazioni cultura invarianti. Ad esempio, la conversione nel tipo int CLR o DateTime. Questi vincoli presuppongono che l'URL non sia localizzabile. I vincoli di route specificati dal framework non modificano i valori archiviati nei valori di route. Tutti i valori di route analizzati dall'URL vengono archiviati come stringhe. Ad esempio, il vincolo float prova a convertire il valore di route in un valore float, ma il valore convertito viene usato solo per verificare che può essere convertito in un valore float.

Espressioni regolari nei vincoli

Avviso

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

Le espressioni regolari possono essere specificate come vincoli inline usando il vincolo di regex(...) route. I metodi nella MapControllerRoute famiglia accettano anche un valore letterale oggetto di vincoli. Se viene utilizzata la maschera, i valori stringa vengono interpretati come espressioni regolari.

Il codice seguente usa un vincolo regex inline:

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
        context => 
        {
            return context.Response.WriteAsync("inline-constraint match");
        });
 });

Il codice seguente usa un valore letterale oggetto per specificare un vincolo regex:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "people",
        pattern: "People/{ssn}",
        constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
        defaults: new { controller = "People", action = "List", });
});

Il framework di ASP.NET Core aggiunge RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant al costruttore di espressioni regolari. Per una descrizione di questi membri, vedere RegexOptions.

Le espressioni regolari usano delimitatori e token simili a quelli usati dal routing e dal linguaggio C#. Per i token di espressione è necessario aggiungere caratteri di escape. Per usare l'espressione ^\d{3}-\d{2}-\d{4}$ regolare in un vincolo inline, usare una delle opzioni seguenti:

  • Sostituire i \ caratteri specificati nella stringa come \\ caratteri nel file di origine C# per eseguire l'escape del carattere di escape della \ stringa.
  • Valori letterali stringa verbatim.

Per eseguire l'escape dei caratteri {delimitatori dei parametri di routing , [}, , ]double i caratteri nell'espressione, ad esempio , [[{{}}, , . ]] La tabella seguente mostra un'espressione regolare e la relativa versione con escape:

Espressione regolare Espressione regolare preceduta da escape
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Le espressioni regolari usate nel routing spesso iniziano con il ^ carattere e corrispondono alla posizione iniziale della stringa. Le espressioni terminano spesso con il $ carattere e corrispondono alla fine della stringa. I ^ caratteri e $ assicurano che l'espressione regolare corrisponda all'intero valore del parametro di route. Senza i ^ caratteri e $ , l'espressione regolare corrisponde a qualsiasi sottostringa all'interno della stringa, spesso indesiderata. La tabella seguente fornisce esempi e spiega il motivo per cui corrispondono o non corrispondono:

Espressione String Match Commento
[a-z]{2} hello Corrispondenze di sottostringhe
[a-z]{2} 123abc456 Corrispondenze di sottostringhe
[a-z]{2} mz Corrisponde all'espressione
[a-z]{2} MZ Senza distinzione maiuscole/minuscole
^[a-z]{2}$ hello No Vedere ^ e $ sopra
^[a-z]{2}$ 123abc456 No Vedere ^ e $ sopra

Per altre informazioni sulla sintassi delle espressioni regolari, vedere Espressioni regolari di .NET Framework.

Per limitare un parametro a un set noto di valori possibili, usare un'espressione regolare. Ad esempio, {action:regex(^(list|get|create)$)} verifica la corrispondenza del valore di route action solo con list, get o create. Se viene passata nel dizionario di vincoli, la stringa ^(list|get|create)$ è equivalente. I vincoli passati nel dizionario vincoli che non corrispondono a uno dei vincoli noti vengono considerati anche come espressioni regolari. I vincoli passati all'interno di un modello che non corrispondono a uno dei vincoli noti non vengono considerati come espressioni regolari.

Vincoli di route personalizzati

È possibile creare vincoli di route personalizzati implementando l'interfaccia IRouteConstraint . L'interfaccia IRouteConstraint contiene Match, che restituisce true se il vincolo è soddisfatto e false in caso contrario.

I vincoli di route personalizzati sono raramente necessari. Prima di implementare un vincolo di route personalizzato, prendere in considerazione alternative, ad esempio l'associazione di modelli.

La cartella ASP.NET vincoli principali fornisce esempi validi di creazione di vincoli. Ad esempio, GuidRouteConstraint.

Per usare un oggetto personalizzato IRouteConstraint, il tipo di vincolo di route deve essere registrato con l'app ConstraintMap nel contenitore del servizio. ConstraintMap è un dizionario che esegue il mapping delle chiavi dei vincoli di route alle implementazioni di IRouteConstraint che convalidano tali vincoli. La proprietà ConstraintMap di un'app può essere aggiornata in Startup.ConfigureServices come parte di una chiamata services.AddRouting o configurando RouteOptions direttamente con services.Configure<RouteOptions>. Ad esempio:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddRouting(options =>
    {
        options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
    });
}

Il vincolo precedente viene applicato nel codice seguente:

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    // GET /api/test/3
    [HttpGet("{id:customName}")]
    public IActionResult Get(string id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // GET /api/test/my/3
    [HttpGet("my/{id:customName}")]
    public IActionResult Get(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

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

L'implementazione di MyCustomConstraint impedisce l'applicazione 0 a un parametro di route:

class MyCustomConstraint : IRouteConstraint
{
    private Regex _regex;

    public MyCustomConstraint()
    {
        _regex = new Regex(@"^[1-9]*$",
                            RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
                            TimeSpan.FromMilliseconds(100));
    }
    public bool Match(HttpContext httpContext, IRouter route, string routeKey,
                      RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values.TryGetValue(routeKey, out object value))
        {
            var parameterValueString = Convert.ToString(value,
                                                        CultureInfo.InvariantCulture);
            if (parameterValueString == null)
            {
                return false;
            }

            return _regex.IsMatch(parameterValueString);
        }

        return false;
    }
}

Avviso

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

Il codice precedente:

  • Impedisce 0 nel {id} segmento della route.
  • Viene illustrato come fornire un esempio di base dell'implementazione di un vincolo personalizzato. Non deve essere usato in un'app di produzione.

Il codice seguente è un approccio migliore per impedire l'elaborazione di un oggetto id contenente un 0 oggetto :

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return ControllerContext.MyDisplayRouteInfo(id);
}

Il codice precedente presenta i vantaggi seguenti rispetto all'approccio MyCustomConstraint :

  • Non richiede un vincolo personalizzato.
  • Restituisce un errore più descrittivo quando il parametro di route include 0.

Riferimento ai trasformatori di parametro

I trasformatori di parametro:

Ad esempio, un trasformatore di parametro slugify personalizzato nel modello di route blog\{article:slugify} con Url.Action(new { article = "MyTestArticle" }) genera blog\my-test-article.

Si consideri l'implementazione seguente IOutboundParameterTransformer:

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

Per usare un trasformatore di parametro in un modello di route, configurarlo usando ConstraintMap in Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddRouting(options =>
    {
        options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
    });
}

Il framework ASP.NET Core usa trasformatori di parametro per trasformare l'URI in cui viene risolto un endpoint. Ad esempio, i trasformatori di parametro trasformano i valori di route usati per trovare la corrispondenza con un areaoggetto , controller, actione page.

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

Con il modello di route precedente, l'azione SubscriptionManagementController.GetAll viene confrontata con l'URI /subscription-management/get-all. Un trasformatore di parametro non modifica i valori della route usati per generare un collegamento. Ad esempio, Url.Action("GetAll", "SubscriptionManagement") restituisce /subscription-management/get-all.

ASP.NET Core fornisce convenzioni API per l'uso di trasformatori di parametri con route generate:

Riferimento per la generazione di URL

Questa sezione contiene un riferimento per l'algoritmo implementato dalla generazione di URL. In pratica, esempi più complessi di generazione url usano controller o Razor pagine. Per altre informazioni, vedere Routing nei controller .

Il processo di generazione url inizia con una chiamata a LinkGenerator.GetPathByAddress o a un metodo simile. Il metodo viene fornito con un indirizzo, un set di valori di route e facoltativamente informazioni sulla richiesta corrente da HttpContext.

Il primo passaggio consiste nell'usare l'indirizzo per risolvere un set di endpoint candidati usando un oggetto IEndpointAddressScheme<TAddress> che corrisponde al tipo di indirizzo.

Dopo che il set di candidati viene trovato dallo schema di indirizzi, gli endpoint vengono ordinati ed elaborati in modo iterativo fino a quando non viene completata un'operazione di generazione url. La generazione di URL non verifica la presenza di ambiguità, il primo risultato restituito è il risultato finale.

Risoluzione dei problemi relativi alla generazione di URL con la registrazione

Il primo passaggio per la risoluzione dei problemi relativi alla generazione di URL consiste nell'impostare il livello di registrazione di Microsoft.AspNetCore.Routing su TRACE. LinkGenerator registra molti dettagli sull'elaborazione che può essere utile per risolvere i problemi.

Per informazioni dettagliate sulla generazione di URL, vedere Informazioni di riferimento sulla generazione di URL.

Indirizzi

Gli indirizzi sono il concetto nella generazione di URL usato per associare una chiamata al generatore di collegamenti a un set di endpoint candidati.

Gli indirizzi sono un concetto estendibile che include due implementazioni per impostazione predefinita:

  • Uso del nome dell'endpoint (string) come indirizzo:
    • Fornisce funzionalità simili al nome della route di MVC.
    • Usa il IEndpointNameMetadata tipo di metadati.
    • Risolve la stringa fornita rispetto ai metadati di tutti gli endpoint registrati.
    • Genera un'eccezione all'avvio se più endpoint usano lo stesso nome.
    • Consigliato per l'uso generico all'esterno di controller e Razor pagine.
  • Uso dei valori di route (RouteValuesAddress) come indirizzo:
    • Fornisce funzionalità simili ai controller e Razor alla generazione di URL legacy di Pages.
    • Molto complesso da estendere ed eseguire il debug.
    • Fornisce l'implementazione usata da IUrlHelper, helper tag, helper HTML, risultati azione e così via.

Il ruolo dello schema di indirizzi consiste nell'eseguire l'associazione tra l'indirizzo e gli endpoint corrispondenti in base a criteri arbitrari:

  • Lo schema dei nomi dell'endpoint esegue una ricerca di dizionario di base.
  • Lo schema dei valori di route ha un subset migliore complesso dell'algoritmo set.

Valori di ambiente e valori espliciti

Dalla richiesta corrente, il routing accede ai valori di route della richiesta HttpContext.Request.RouteValuescorrente. I valori associati alla richiesta corrente vengono definiti valori di ambiente. Ai fini della chiarezza, la documentazione fa riferimento ai valori di route passati ai metodi come valori espliciti.

Nell'esempio seguente vengono illustrati i valori di ambiente e i valori espliciti. Fornisce valori di ambiente dalla richiesta corrente e valori espliciti: : { id = 17, }

public class WidgetController : Controller
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public IActionResult Index()
    {
        var url = _linkGenerator.GetPathByAction(HttpContext,
                                                 null, null,
                                                 new { id = 17, });
        return Content(url);
    }

Il codice precedente:

Il codice seguente non fornisce valori di ambiente e valori espliciti: : { controller = "Home", action = "Subscribe", id = 17, }

public IActionResult Index2()
{
    var url = _linkGenerator.GetPathByAction("Subscribe", "Home",
                                             new { id = 17, });
    return Content(url);
}

Il metodo precedente restituisce /Home/Subscribe/17

Il codice seguente in WidgetController restituisce /Widget/Subscribe/17:

var url = _linkGenerator.GetPathByAction("Subscribe", null,
                                         new { id = 17, });

Il codice seguente fornisce il controller dai valori di ambiente nella richiesta corrente e nei valori espliciti: : { action = "Edit", id = 17, }

public class GadgetController : Controller
{
    public IActionResult Index()
    {
        var url = Url.Action("Edit", new { id = 17, });
        return Content(url);
    }

Nel codice precedente:

  • /Gadget/Edit/17 viene restituito.
  • Url ottiene l'oggetto IUrlHelper.
  • Action genera un URL con un percorso assoluto per un metodo di azione. L'URL contiene il nome e route i valori specificatiaction.

Il codice seguente fornisce i valori di ambiente della richiesta corrente e dei valori espliciti: : { page = "./Edit, id = 17, }

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var url = Url.Page("./Edit", new { id = 17, });
        ViewData["URL"] = url;
    }
}

Il codice precedente imposta su url /Edit/17 quando la pagina di modifica Razor contiene la direttiva di pagina seguente:

@page "{id:int}"

Se la pagina Modifica non contiene il "{id:int}" modello di route, url è /Edit?id=17.

Il comportamento di MVC IUrlHelper aggiunge un livello di complessità oltre alle regole descritte di seguito:

  • IUrlHelper fornisce sempre i valori di route della richiesta corrente come valori di ambiente.
  • IUrlHelper.Action copia sempre i valori correnti action e controller di route come valori espliciti, a meno che non venga sottoposto a override dallo sviluppatore.
  • IUrlHelper.Page copia sempre il valore di route corrente page come valore esplicito, a meno che non venga sottoposto a override.
  • IUrlHelper.Page esegue sempre l'override del valore di route corrente handler con null come valori espliciti, a meno che non venga sottoposto a override.

Gli utenti sono spesso sorpresi dai dettagli comportamentali dei valori di ambiente, perché MVC non sembra seguire le proprie regole. Per motivi cronologici e di compatibilità, alcuni valori di route, actionad esempio , controllerpage, e handler hanno un comportamento specifico.

La funzionalità equivalente fornita da LinkGenerator.GetPathByAction e LinkGenerator.GetPathByPage duplica queste anomalie di IUrlHelper per la compatibilità.

Processo di generazione url

Dopo aver trovato il set di endpoint candidati, l'algoritmo di generazione url:

  • Elabora gli endpoint in modo iterativo.
  • Restituisce il primo risultato riuscito.

Il primo passaggio di questo processo è denominato invalidazione del valore di route. L'invalidazione del valore di route è il processo in base al quale il routing decide quali valori di route dai valori di ambiente devono essere usati e quali devono essere ignorati. Ogni valore di ambiente viene considerato e combinato con i valori espliciti o ignorato.

Il modo migliore per considerare il ruolo dei valori di ambiente è che tentano di salvare gli sviluppatori di applicazioni digitando, in alcuni casi comuni. Tradizionalmente, gli scenari in cui i valori di ambiente sono utili sono correlati a MVC:

  • Quando si esegue il collegamento a un'altra azione nello stesso controller, non è necessario specificare il nome del controller.
  • Quando si esegue il collegamento a un altro controller nella stessa area, non è necessario specificare il nome dell'area.
  • Quando si esegue il collegamento allo stesso metodo di azione, non è necessario specificare i valori di route.
  • Quando si esegue il collegamento a un'altra parte dell'app, non si vogliono trasportare valori di route che non hanno alcun significato in tale parte dell'app.

Le chiamate a LinkGenerator o IUrlHelper che restituiscono null sono in genere causate dalla mancata comprensione dell'invalidazione del valore di route. Risolvere i problemi di invalidazione del valore di route specificando in modo esplicito più valori di route per verificare se questo risolve il problema.

L'invalidazione del valore di route si basa sul presupposto che lo schema URL dell'app sia gerarchico, con una gerarchia formata da sinistra a destra. Si consideri il modello {controller}/{action}/{id?} di route del controller di base per avere un'idea intuitiva del funzionamento di questa operazione in pratica. Una modifica a un valore invalida tutti i valori di route visualizzati a destra. Ciò riflette il presupposto sulla gerarchia. Se l'app ha un valore di ambiente per ide l'operazione specifica un valore diverso per :controller

  • id non verrà riutilizzato perché {controller} si trova a sinistra di {id?}.

Alcuni esempi che illustrano questo principio:

  • Se i valori espliciti contengono un valore per id, il valore di ambiente per id viene ignorato. I valori di ambiente per controller e action possono essere usati.
  • Se i valori espliciti contengono un valore per action, qualsiasi valore di ambiente per action viene ignorato. È possibile usare i valori di ambiente per controller . Se il valore esplicito per action è diverso dal valore di ambiente per action, il id valore non verrà usato. Se il valore esplicito per action è uguale al valore di ambiente per action, è possibile usare il id valore .
  • Se i valori espliciti contengono un valore per controller, qualsiasi valore di ambiente per controller viene ignorato. Se il valore esplicito per controller è diverso dal valore di ambiente per controller, i action valori e id non verranno usati. Se il valore esplicito per controller è uguale al valore di ambiente per controller, è possibile usare i action valori e id .

Questo processo è ulteriormente complicato dall'esistenza di route di attributi e route convenzionali dedicate. Route convenzionali del controller, ad {controller}/{action}/{id?} esempio specificare una gerarchia usando i parametri di route. Per route convenzionali dedicate e route di attributi ai controller e Razor alle pagine:

  • Esiste una gerarchia di valori di route.
  • Non vengono visualizzati nel modello.

Per questi casi, la generazione di URL definisce il concetto di valori necessari. Gli endpoint creati dai controller e Razor pagine hanno valori obbligatori specificati che consentono il funzionamento dell'invalidazione del valore di route.

Algoritmo di invalidazione del valore della route in dettaglio:

  • I nomi dei valori obbligatori vengono combinati con i parametri di route, quindi elaborati da sinistra a destra.
  • Per ogni parametro vengono confrontati il valore di ambiente e il valore esplicito:
    • Se il valore di ambiente e il valore esplicito sono uguali, il processo continua.
    • Se il valore di ambiente è presente e il valore esplicito non è , il valore di ambiente viene usato durante la generazione dell'URL.
    • Se il valore di ambiente non è presente e il valore esplicito è , rifiutare il valore di ambiente e tutti i valori di ambiente successivi.
    • Se sono presenti il valore di ambiente e il valore esplicito e i due valori sono diversi, rifiutare il valore di ambiente e tutti i valori di ambiente successivi.

A questo punto, l'operazione di generazione url è pronta per valutare i vincoli di route. Il set di valori accettati viene combinato con i valori predefiniti del parametro, forniti ai vincoli. Se tutti i vincoli vengono passati, l'operazione continua.

Successivamente, i valori accettati possono essere usati per espandere il modello di route. Il modello di route viene elaborato:

  • Da sinistra a destra.
  • Ogni parametro ha il valore accettato sostituito.
  • Con i casi speciali seguenti:
    • Se i valori accettati mancano un valore e il parametro ha un valore predefinito, viene usato il valore predefinito.
    • Se i valori accettati mancano un valore e il parametro è facoltativo, l'elaborazione continua.
    • Se un parametro di route a destra di un parametro facoltativo mancante ha un valore, l'operazione ha esito negativo.
    • I parametri con valori predefiniti contigui e i parametri facoltativi vengono compressi laddove possibile.

I valori specificati in modo esplicito che non corrispondono a un segmento della route vengono aggiunti alla stringa di query. La tabella seguente illustra il risultato ottenuto quando si usa il modello di route {controller}/{action}/{id?}.

Valori di ambiente Valori espliciti Risultato
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", colore = "Rosso" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

Problemi con l'invalidazione del valore di route

A partire da ASP.NET Core 3.0, alcuni schemi di generazione url usati nelle versioni precedenti di ASP.NET Core non funzionano correttamente con la generazione di URL. Il team di ASP.NET Core prevede di aggiungere funzionalità per soddisfare queste esigenze in una versione futura. Per il momento la soluzione migliore consiste nell'usare il routing legacy.

Il codice seguente illustra un esempio di schema di generazione url non supportato dal routing.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("default", 
                                     "{culture}/{controller=Home}/{action=Index}/{id?}");
    endpoints.MapControllerRoute("blog", "{culture}/{**slug}", 
                                      new { controller = "Blog", action = "ReadPost", });
});

Nel codice precedente il parametro di route viene usato per la culture localizzazione. Il desiderio è avere il culture parametro sempre accettato come valore di ambiente. Tuttavia, il culture parametro non viene accettato come valore di ambiente a causa del funzionamento dei valori richiesti:

  • "default" Nel modello di route il culture parametro di route si trova a sinistra di controller, quindi le modifiche apportate a controller non invalidano culture.
  • "blog" Nel modello di route il culture parametro di route viene considerato a destra di controller, che viene visualizzato nei valori necessari.

Configurazione dei metadati dell'endpoint

I collegamenti seguenti forniscono informazioni sulla configurazione dei metadati dell'endpoint:

Corrispondenza dell'host nelle route con RequireHost

RequireHost applica un vincolo alla route che richiede l'host specificato. Il RequireHost parametro o [Host] può essere:

  • Host: www.domain.com, corrisponde www.domain.com a qualsiasi porta.
  • Host con carattere jolly: *.domain.com, corrisponde www.domain.coma , subdomain.domain.como www.subdomain.domain.com su qualsiasi porta.
  • Porta: *:5000, corrisponde alla porta 5000 con qualsiasi host.
  • Host e porta: www.domain.com:5000 o *.domain.com:5000, corrisponde all'host e alla porta.

È possibile specificare più parametri usando RequireHost o [Host]. Il vincolo corrisponde agli host validi per uno dei parametri. Ad esempio, [Host("domain.com", "*.domain.com")] corrisponde a domain.com, www.domain.come subdomain.domain.com.

Il codice seguente usa RequireHost per richiedere l'host specificato nella route:

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", context => context.Response.WriteAsync("Hi Contoso!"))
            .RequireHost("contoso.com");
        endpoints.MapGet("/", context => context.Response.WriteAsync("AdventureWorks!"))
            .RequireHost("adventure-works.com");
        endpoints.MapHealthChecks("/healthz").RequireHost("*:8080");
    });
}

Il codice seguente usa l'attributo [Host] nel controller per richiedere uno degli host specificati:

[Host("contoso.com", "adventure-works.com")]
public class ProductController : Controller
{
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Host("example.com:8080")]
    public IActionResult Privacy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Quando l'attributo [Host] viene applicato sia al controller che al metodo di azione:

  • Viene usato l'attributo sull'azione.
  • L'attributo controller viene ignorato.

Indicazioni sulle prestazioni per il routing

La maggior parte del routing è stata aggiornata in ASP.NET Core 3.0 per migliorare le prestazioni.

Quando un'app presenta problemi di prestazioni, il routing viene spesso sospettato come problema. Il routing è sospetto che framework come controller e Razor pagine segnalano la quantità di tempo impiegato all'interno del framework nei messaggi di registrazione. Quando si verifica una differenza significativa tra il tempo segnalato dai controller e il tempo totale della richiesta:

  • Gli sviluppatori eliminano il codice dell'app come origine del problema.
  • È comune presupporre che il routing sia la causa.

Il routing viene testato usando migliaia di endpoint. È improbabile che un'app tipica incontri un problema di prestazioni semplicemente essendo troppo grande. La causa radice più comune delle prestazioni di routing lente è in genere un middleware personalizzato con comportamento non valido.

Questo esempio di codice seguente illustra una tecnica di base per restringere l'origine del ritardo:

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseRouting();

    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseAuthorization();

    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Timing test.");
        });
    });
}

Routing temporizzato:

  • Interleave ogni middleware con una copia del middleware di intervallo illustrato nel codice precedente.
  • Aggiungere un identificatore univoco per correlare i dati di intervallo con il codice.

Si tratta di un modo di base per limitare il ritardo quando è significativo, ad esempio, più di 10ms. Sottraendo Time 2 dai Time 1 report il tempo trascorso all'interno del UseRouting middleware.

Il codice seguente usa un approccio più compatto al codice di intervallo precedente:

public sealed class MyStopwatch : IDisposable
{
    ILogger<Startup> _logger;
    string _message;
    Stopwatch _sw;

    public MyStopwatch(ILogger<Startup> logger, string message)
    {
        _logger = logger;
        _message = message;
        _sw = Stopwatch.StartNew();
    }

    private bool disposed = false;


    public void Dispose()
    {
        if (!disposed)
        {
            _logger.LogInformation("{Message }: {ElapsedMilliseconds}ms",
                                    _message, _sw.ElapsedMilliseconds);

            disposed = true;
        }
    }
}
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    int count = 0;
    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }

    });

    app.UseRouting();

    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }
    });

    app.UseAuthorization();

    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Timing test.");
        });
    });
}

Funzionalità di routing potenzialmente costose

L'elenco seguente fornisce alcune informazioni dettagliate sulle funzionalità di routing relativamente costose rispetto ai modelli di route di base:

  • Espressioni regolari: è possibile scrivere espressioni regolari complesse o con tempi di esecuzione lunghi con una piccola quantità di input.
  • Segmenti complessi ({x}-{y}-{z}):
    • Sono notevolmente più costosi rispetto all'analisi di un segmento di percorso URL normale.
    • Comporta l'allocazione di molte più sottostringhe.
    • La logica dei segmenti complessi non è stata aggiornata in ASP.NET aggiornamento delle prestazioni del routing core 3.0.
  • Accesso ai dati sincrono: molte app complesse hanno accesso al database come parte del routing. ASP.NET Core 2.2 e versioni precedenti potrebbero non fornire i punti di estendibilità corretti per supportare il routing dell'accesso al database. Ad esempio, IRouteConstrainte IActionConstraint sono sincroni. I punti di estendibilità, MatcherPolicy ad esempio e EndpointSelectorContext , sono asincroni.

Linee guida per gli autori di librerie

Questa sezione contiene indicazioni per gli autori di librerie che si basano sul routing. Questi dettagli sono progettati per garantire che gli sviluppatori di app abbiano un'esperienza ottimale usando librerie e framework che estendono il routing.

Definire gli endpoint

Per creare un framework che usa il routing per la corrispondenza degli URL, iniziare definendo un'esperienza utente basata su UseEndpoints.

Do build on top of IEndpointRouteBuilder. In questo modo gli utenti possono comporre il framework con altre funzionalità di ASP.NET Core senza confusione. Ogni modello ASP.NET Core include il routing. Si supponga che il routing sia presente e familiare per gli utenti.

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...);

    endpoints.MapHealthChecks("/healthz");
});

DO restituisce un tipo di cemento sigillato da una chiamata a MapMyFramework(...) che implementa IEndpointConventionBuilder. La maggior parte dei metodi del framework Map... segue questo modello. Interfaccia IEndpointConventionBuilder :

  • Consente la componibilità dei metadati.
  • È destinato a un'ampia gamma di metodi di estensione.

La dichiarazione del proprio tipo consente di aggiungere funzionalità specifiche del framework al generatore. È possibile eseguire il wrapping di un generatore dichiarato dal framework e inoltrare le chiamate.

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization()
                                 .WithMyFrameworkFeature(awesome: true);

    endpoints.MapHealthChecks("/healthz");
});

PRENDERE IN CONSIDERAZIONE la scrittura di un proprio EndpointDataSourceoggetto . EndpointDataSource è la primitiva di basso livello per dichiarare e aggiornare una raccolta di endpoint. EndpointDataSource è un'API potente usata dai controller e Razor dalle pagine.

I test di routing hanno un esempio di base di un'origine dati non aggiornata.

NON tentare di registrare un oggetto EndpointDataSource per impostazione predefinita. Richiedere agli utenti di registrare il framework in UseEndpoints. La filosofia del routing è che nulla è incluso per impostazione predefinita e che UseEndpoints è la posizione in cui registrare gli endpoint.

Creazione di middleware integrato nel routing

CONSIDERARE la definizione dei tipi di metadati come interfaccia.

DO consente di usare i tipi di metadati come attributo per classi e metodi.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Framework come controller e Razor pagine supportano l'applicazione di attributi di metadati a tipi e metodi. Se dichiari i tipi di metadati:

  • Renderli accessibili come attributi.
  • La maggior parte degli utenti ha familiarità con l'applicazione degli attributi.

La dichiarazione di un tipo di metadati come interfaccia aggiunge un altro livello di flessibilità:

  • Le interfacce sono componibili.
  • Gli sviluppatori possono dichiarare i propri tipi che combinano più criteri.

FARE in modo che sia possibile eseguire l'override dei metadati, come illustrato nell'esempio seguente:

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Il modo migliore per seguire queste linee guida consiste nell'evitare di definire i metadati del marcatore:

  • Non cercare solo la presenza di un tipo di metadati.
  • Definire una proprietà sui metadati e controllare la proprietà .

La raccolta di metadati viene ordinata e supporta l'override in base alla priorità. Nel caso dei controller, i metadati nel metodo di azione sono più specifici.

FARE in modo che il middleware sia utile con e senza routing.

app.UseRouting();

app.UseAuthorization(new AuthorizationPolicy() { ... });

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization();
});

Come esempio di questa linea guida, prendere in considerazione il UseAuthorization middleware. Il middleware di autorizzazione consente di passare un criterio di fallback. I criteri di fallback, se specificati, si applicano a entrambi:

  • Endpoint senza criteri specificati.
  • Richieste che non corrispondono a un endpoint.

In questo modo il middleware di autorizzazione risulta utile al di fuori del contesto del routing. Il middleware di autorizzazione può essere usato per la programmazione middleware tradizionale.

Eseguire il debug della diagnostica

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

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