Middleware di ASP.NET Core

Di Rick Anderson e Steve Smith

Il middleware è un software che viene assemblato in una pipeline dell'app per gestire richieste e risposte. Ogni componente:

  • Sceglie se passare la richiesta al componente successivo nella pipeline.
  • Può eseguire le operazioni prima e dopo il componente successivo nella pipeline.

Per compilare la pipeline delle richieste vengono usati i delegati di richiesta. I delegati di richiesta gestiscono ogni richiesta HTTP.

I delegati di richiesta vengono configurati tramite i metodi di estensione Run, Map e Use. È possibile specificare un singolo delegato di richiesta inline come metodo anonimo (chiamato middleware inline) o definirlo in una classe riutilizzabile. Le classi riutilizzabili o i metodi anonimi inline costituiscono il middleware o sono noti anche come componenti middleware. Ogni componente middleware è responsabile della chiamata del componente seguente nella pipeline o del corto circuito della pipeline. Quando un middleware esegue un corto circuito, viene denominato middleware terminale perché impedisce all'ulteriore middleware di elaborare la richiesta.

In Eseguire la migrazione di moduli HTTP in middleware vengono spiegate le differenze tra le pipeline delle richieste in ASP.NET Core e in ASP.NET 4.x e sono riportati altri esempi di middleware.

Analisi del codice middleware

ASP.NET Core include molti analizzatori della piattaforma del compilatore che controllano la qualità del codice dell'applicazione. Per altre informazioni, vedere Analisi del codice nelle app di ASP.NET Core.

Creare una pipeline middleware con WebApplication

La pipeline delle richieste ASP.NET Core è costituita da una sequenza di delegati di richiesta, chiamati uno dopo l'altro. Il diagramma seguente illustra il concetto. Il thread di esecuzione seguente le frecce nere.

Modello di elaborazione delle richieste che visualizza una richiesta in arrivo, l'elaborazione tramite tre middleware e la risposta che esce dall'app. Ogni middleware esegue la relativa logica e passa la richiesta al middleware successivo in corrispondenza dell'istruzione next(). Dopo che il terzo middleware ha elaborato la richiesta, la richiesta torna ai due middleware precedenti in ordine inverso per un'ulteriore elaborazione dopo le istruzioni next() prima di uscire dall'app sotto forma di risposta al client.

I delegati possono eseguire le operazioni prima del delegato successivo e dopo di esso. I delegati che gestiscono le eccezioni devono essere chiamati nella prima parte della pipeline in modo che possano individuare le eccezioni che si verificano nelle parti successive della pipeline.

L'app di ASP.NET Core più semplice imposta un delegato di richiesta singolo che gestisce tutte le richieste. In questo caso non è inclusa una pipeline di richieste effettiva. Al contrario, viene chiamata una singola funzione anonima in risposta a ogni richiesta HTTP.

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

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

È possibile concatenare più delegati di richiesta insieme con Use. Il parametro next rappresenta il delegato successivo nella pipeline. È possibile eseguire il corto circuito della pipeline non chiamando il parametro next. In genere è possibile eseguire un'azione prima e dopo il delegato next, come illustra l'esempio seguente:

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

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Quando un delegato non passa una richiesta al delegato successivo, si crea un cosiddetto corto circuito della pipeline delle richieste. Il corto circuito è spesso opportuno poiché evita l'esecuzione di operazioni non necessarie. Ad esempio, il middleware dei file statici può operare come middleware terminale elaborando una richiesta di un file statico ed effettuando il corto circuito della pipeline rimanente. Il middleware aggiunto alla pipeline prima del middleware che termina l'ulteriore elaborazione elabora comunque il codice dopo le istruzioni next.Invoke. Vedere comunque l'avviso seguente sul tentativo di scrivere una risposta che è già stata inviata.

Avviso

Non chiamare next.Invoke dopo aver inviato la risposta al client. Le modifiche apportate a HttpResponse dopo l'avvio della risposta generano un'eccezione. Ad esempio, l'impostazione delle intestazioni e di un codice di stato generano un'eccezione. Scrivere nel corpo della risposta dopo aver chiamato next:

  • Può causare una violazione del protocollo. Ad esempio, scrivere un contenuto che supera il valore Content-Length specificato.
  • Può danneggiare il formato del corpo. Ad esempio, scrivere un piè di pagina HTML in un file CSS.

HasStarted è un hint utile per indicare se le intestazioni sono state inviate o se è stato scritto contenuto nel corpo.

I delegati Run non ricevono un parametro next. Il primo delegato Run è sempre terminale e termina la pipeline. Run è una convenzione. Alcuni componenti del middleware possono esporre metodi Run[Middleware] che vengono eseguiti al termine della pipeline:

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

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Per visualizzare i commenti del codice tradotti in lingue diverse dall'inglese, segnalarlo in questo problema di discussione su GitHub.

Nell'esempio precedente il delegato Run scrive "Hello from 2nd delegate." nella risposta e quindi termina la pipeline. Se un altro delegato Use o Run viene aggiunto dopo il delegato Run, non viene chiamato.

Preferire l'overload app.Use che richiede il passaggio del contesto a next

Il metodo di estensione app.Use senza allocazione:

  • Richiede il passaggio del contesto a next.
  • Salva due allocazioni interne per richiesta necessarie quando si usa l'altro overload.

Per altre informazioni, vedere questo problema in GitHub.

Ordine del middleware

Il diagramma seguente illustra la pipeline di elaborazione completa delle richieste per app ASP.NET Core MVC e Razor Pages. È possibile vedere come, in un'app tipica, vengono ordinati i middleware esistenti e dove vengono aggiunti i middleware personalizzati. Si ha il controllo completo su come riordinare i middleware esistenti o inserire nuovi middleware personalizzati in base alle esigenze per gli specifici scenari.

Pipeline del middleware di ASP.NET Core

Il middleware Endpoint nel diagramma precedente esegue la pipeline di filtro per il tipo di app corrispondente, MVC o Razor Pages.

Il middleware Routing nel diagramma precedente è illustrato dopo File statici. Questo è l'ordine implementato dai modelli di progetto chiamando in modo esplicito app.UseRouting. Se non si chiama app.UseRouting, il middleware Routing viene eseguito all'inizio della pipeline per impostazione predefinita. Per altre informazioni, vedere Routing.

Pipeline di filtro di ASP.NET Core

L'ordine di aggiunta dei componenti middleware nel file Program.cs definisce l'ordine in cui i componenti middleware vengono richiamati per le richieste e l'ordine inverso per la risposta. Questo ordinamento è fondamentale per la sicurezza, le prestazioni e la funzionalità.

Il codice evidenziato seguente in Program.cs aggiunge componenti middleware correlati alla sicurezza nell'ordine consigliato tipico:

using IndividualAccountsExample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

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

app.Run();

Nel codice precedente:

  • Il middleware che non viene aggiunto durante la creazione di una nuova app Web con singoli account utente viene impostato come commento.
  • Non tutti i middleware vengono visualizzati in questo ordine esatto, ma molti lo fanno. Ad esempio:
    • UseCors, UseAuthentication e UseAuthorization devono essere visualizzati nell'ordine visualizzato.
    • UseCors attualmente deve essere visualizzato prima di UseResponseCaching. Questo requisito è illustrato nel problema dotnet/aspnetcore n. 23218 in GitHub.
    • UseRequestLocalization deve comparire prima di qualsiasi middleware che potrebbe controllare le impostazioni cultura della richiesta (ad esempio, app.UseMvcWithDefaultRoute()).

In alcuni scenari, il middleware ha un ordinamento diverso. Ad esempio, l'ordinamento della memorizzazione nella cache e della compressione è specifico dello scenario e sono disponibili più ordinamenti validi. Ad esempio:

app.UseResponseCaching();
app.UseResponseCompression();

Con il codice precedente, l'utilizzo della CPU può essere ridotto memorizzando nella cache la risposta compressa, ma ci si potrebbe ritrovare a memorizzare nella cache più rappresentazioni di una risorsa usando algoritmi di compressione diversi, ad esempio Gzip o Brotli.

L'ordinamento seguente combina i file statici per consentire la memorizzazione nella cache di file statici compressi:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

Il codice Program.cs seguente aggiunge componenti del middleware per gli scenari di app comuni:

  1. Gestione errori/eccezioni
    • Quando l'app viene eseguita nell'ambiente di sviluppo:
      • Il middleware della pagina delle eccezioni per gli sviluppatori (UseDeveloperExceptionPage) segnala gli errori di runtime delle app.
      • Il middleware della pagina degli errori del database (UseDatabaseErrorPage) segnala gli errori di runtime del database.
    • Quando l'app viene eseguita nell'ambiente di produzione:
      • Il middleware del gestore delle eccezioni (UseExceptionHandler) intercetta le eccezioni generate nei middleware seguenti.
      • Il middleware del protocollo HSTS (HTTP Strict Transport Security) (UseHsts) aggiunge l'intestazione Strict-Transport-Security.
  2. Il middleware di reindirizzamento HTTPS (UseHttpsRedirection) reindirizza le richieste HTTP a HTTPS.
  3. Il middleware dei file statici (UseStaticFiles) restituisce i file statici e impedisce ulteriori elaborazioni della richiesta.
  4. Il middleware dei criteri per i Cookie (UseCookiePolicy) rende l'app conforme al Regolamento generale sulla protezione dei dati (GDPR).
  5. Il middleware di routing (UseRouting) instrada le richieste.
  6. Il middleware di autenticazione (UseAuthentication) tenta di autenticare l'utente prima che sia autorizzato ad accedere a risorse protette.
  7. Il middleware di autorizzazione (UseAuthorization) autorizza un utente ad accedere alle risorse protette.
  8. Il middleware di sessione (UseSession) stabilisce e mantiene aggiornato lo stato sessione. Se l'app usa lo stato sessione, chiamare il middleware di sessione dopo il middleware dei criteri per i Cookie e prima del middleware MVC.
  9. Middleware di routing degli endpoint (UseEndpoints con MapRazorPages) per aggiungere endpoint Razor Pages alla pipeline di richiesta.
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

Nell'esempio di codice precedente, ogni metodo di estensione del middleware viene esposto in WebApplicationBuilder tramite lo spazio dei nomi Microsoft.AspNetCore.Builder.

UseExceptionHandler è il primo componente middleware aggiunto alla pipeline. Pertanto, il middleware del gestore di eccezioni intercetta le eccezioni che si verificano nelle chiamate successive.

Il middleware dei file statici viene chiamato nella prima parte della pipeline in moda che possa gestire le richieste ed eseguire un corto circuito senza passare attraverso i componenti rimanenti. Il middleware dei file statici non offre controlli di autorizzazione. I file serviti dal middleware per i file statici, inclusi i file in wwwroot, sono disponibili pubblicamente. Per informazioni su un approccio alla protezione dei file statici, vedere File statici in ASP.NET Core.

Se la richiesta non è gestita dal middleware dei file statici, viene passata al middleware di autenticazione (UseAuthentication) che esegue l'autenticazione. L'autenticazione non esegue il corto circuito di richieste non autenticate. Sebbene il middleware di autenticazione esegua l'autenticazione delle richieste, l'autorizzazione (e il rifiuto) si verifica solo dopo che MVC seleziona una pagina Razor o un controller MVC specifico e un'azione.

L'esempio seguente illustra un ordinamento del middleware nel quale le richieste dei file statici vengono gestite dal middleware dei file statici prima del middleware di compressione delle risposte. I file statici non vengono compressi con questo ordine di middleware. Le risposte Razor Pages possono essere compresse.

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

Per informazioni sulle applicazioni a pagina singola, vedere le guide per i modelli di progetto React e Angular.

Ordine UseCors e UseStaticFiles

L'ordine per la chiamata di UseCors e UseStaticFiles dipende dall'app. Per altre informazioni, vedere Ordine UseCors e UseStaticFiles

Ordine del middleware delle intestazioni inoltrate

Il middleware delle intestazioni inoltrate deve essere eseguito prima di altro middleware. Questo ordine garantisce che il middleware basato sulle intestazioni inoltrate possa usare i valori di intestazione per l'elaborazione. Per eseguire il middleware delle intestazioni inoltrate dopo il middleware di diagnostica e gestione degli errori, vedere Ordine del middleware delle intestazioni inoltrate.

Creare un ramo nella pipeline del middleware

Le estensioni Map vengono usate come convenzione per la diramazione della pipeline. Map crea un ramo nella pipeline delle richieste in base alle corrispondenze del percorso della richiesta specificato. Se il percorso della richiesta inizia con il percorso specificato, il ramo viene eseguito.

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

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});

app.Run();

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

La tabella seguente visualizza le richieste e le risposte da http://localhost:1234 usando il codice precedente.

Richiesta Risposta
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

Quando si usa Map, i segmenti di percorso corrispondenti vengono rimossi da HttpRequest.Path e aggiunti a HttpRequest.PathBase per ogni richiesta.

Map supporta l'annidamento, ad esempio:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map può anche trovare la corrispondenza di più segmenti contemporaneamente:

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

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen crea un ramo nella pipeline delle richieste in base al risultato del predicato specificato. È possibile usare qualsiasi predicato di tipo Func<HttpContext, bool> per mappare le richieste a un nuovo ramo della pipeline. Nell'esempio seguente viene usato un predicato per rilevare la presenza di una variabile di stringa di query branch:

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

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});

app.Run();

static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

La tabella seguente visualizza le richieste e le risposte da http://localhost:1234 usando il codice precedente:

Richiesta Risposta
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

UseWhen crea anche un ramo nella pipeline delle richieste in base al risultato del predicato specificato. Diversamente da MapWhen, questo ramo viene ricongiunto alla pipeline principale se non esegue il corto circuito o contiene un middleware terminale:

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

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)
{
    var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>(); 

    app.Use(async (context, next) =>
    {
        var branchVer = context.Request.Query["branch"];
        logger.LogInformation("Branch used = {branchVer}", branchVer);

        // Do work that doesn't write to the Response.
        await next();
        // Do other work that doesn't write to the Response.
    });
}

Nell'esempio precedente viene scritta una risposta Hello from non-Map delegate. per tutte le richieste. Se la richiesta include una variabile di stringa di query branch, il relativo valore viene registrato prima del ricongiungimento con la pipeline principale.

Middleware incorporato

ASP.NET Core include i componenti middleware seguenti. Nella colonna Ordinamento sono disponibili note sul posizionamento del middleware nella pipeline di elaborazione delle richieste e indicazioni sulle condizioni nelle quali il middleware può terminare l'elaborazione delle richieste. Quando un middleware esegue un corto circuito della pipeline di elaborazione delle richieste e impedisce al middleware a valle di elaborare una richiesta, viene denominato middleware terminale. Per altre informazioni sul corto circuito, vedere la sezione Creare una pipeline middleware con IApplicationBuilder.

Middleware Descrizione Ordine
autenticazione Offre il supporto dell'autenticazione. Prima di HttpContext.User. Terminale per i callback OAuth.
Autorizzazione Fornisce il supporto per l'autorizzazione. Subito dopo il middleware di autenticazione.
Criteri per i Cookie Registra il consenso degli utenti per l'archiviazione delle informazioni personali e applica gli standard minimi per i campi dei cookie, come secure e SameSite. Prima del middleware che emette i cookie. Esempi: Autenticazione, Sessione, MVC (TempData).
CORS Configura la condivisione di risorse tra le origini (CORS). Prima dei componenti che usano CORS. UseCors attualmente deve precedere UseResponseCaching a causa di questo bug.
DeveloperExceptionPage Genera una pagina con informazioni sugli errori destinate all'uso solo nell'ambiente di sviluppo. Prima dei componenti che generano errori. I modelli di progetto registrano automaticamente questo middleware come primo middleware nella pipeline quando l'ambiente è di sviluppo.
Diagnostica Diversi middleware separati che forniscono una pagina delle eccezioni per sviluppatori, la gestione delle eccezioni, pagine di codici di stato e la pagina Web predefinita per le nuove app. Prima dei componenti che generano errori. Terminale per le eccezioni o per la gestione della pagina Web predefinita per le nuove app.
Intestazioni inoltrate Inoltra le intestazioni proxy nella richiesta corrente. Prima dei componenti che usano i campi aggiornati. Esempi: schema, host, IP client, metodo.
Controllo integrità Controlla l'integrità di un'app ASP.NET Core e le relative dipendenze, come il controllo della disponibilità del database. Terminale se una richiesta corrisponde a un endpoint di controllo di integrità.
Propagazione delle intestazioni Propaga le intestazioni HTTP dalla richiesta in ingresso alle richieste client HTTP in uscita.
Registrazione HTTP Registra le richieste e risposte HTTP. All'inizio della pipeline del middleware.
Override del metodo HTTP Consente a una richiesta POST in arrivo di eseguire l'override del metodo. Prima dei componenti che usano il metodo aggiornato.
Reindirizzamento HTTPS Reindirizzare tutte le richieste HTTP a HTTPS. Prima dei componenti che usano l'URL.
Protocollo HTTP Strict Transport Security (HSTS) Middleware di ottimizzazione della sicurezza che aggiunge un'intestazione della risposta speciale. Prima dell'invio delle risposte e dopo i componenti che modificano le richieste. Esempi: intestazioni inoltrate, riscrittura dell'URL.
MVC Elabora le richieste con MVC/Razor Pages. Terminale se una richiesta corrisponde a una route.
OWIN Interoperabilità con app, server e middleware basati su OWIN. Terminale se la richiesta viene elaborata completamente dal middleware OWIN.
Decompressione richieste Offre il supporto per la decompressione delle richieste. Prima dei componenti che leggono il corpo della richiesta.
Memorizzazione nella cache delle risposte Offre il supporto per la memorizzazione delle risposte nella cache. Prima dei componenti che richiedono la memorizzazione nella cache. UseCORS deve precedere UseResponseCaching.
Compressione delle risposte Offre il supporto per la compressione delle risposte. Prima dei componenti che richiedono la compressione.
Localizzazione della richiesta Offre il supporto per la localizzazione. Prima dei componenti sensibili alla localizzazione. Deve comparire dopo il middleware di routing quando si usa RouteDataRequestCultureProvider.
Routing di endpoint Definisce e vincola le route di richiesta. Terminale per le route corrispondenti.
Applicazione a pagina singola Gestisce tutte le richieste da questo punto nella catena del middleware restituendo la pagina predefinita per l'applicazione a pagina singola In posizione avanzata nella catena, in modo che abbiano la precedenza altri middleware per la gestione di file statici, azioni MVC e così via.
Sessione Offre il supporto per la gestione delle sessioni utente. Prima dei componenti che richiedono la Sessione.
File statici Offre il supporto per la gestione di file statici e l'esplorazione directory. Terminale se una richiesta corrisponde a un file.
URL Rewrite (Riscrittura URL) Offre il supporto per la riscrittura degli URL e il reindirizzamento delle richieste. Prima dei componenti che usano l'URL.
W3CLogging Genera i log di accesso al server nel formato di file di log esteso W3C. All'inizio della pipeline del middleware.
WebSocket Abilita il protocollo WebSocket. Prima dei componenti necessari per accettare le richieste WebSocket.

Risorse aggiuntive

Di Rick Anderson e Steve Smith

Il middleware è un software che viene assemblato in una pipeline dell'app per gestire richieste e risposte. Ogni componente:

  • Sceglie se passare la richiesta al componente successivo nella pipeline.
  • Può eseguire le operazioni prima e dopo il componente successivo nella pipeline.

Per compilare la pipeline delle richieste vengono usati i delegati di richiesta. I delegati di richiesta gestiscono ogni richiesta HTTP.

I delegati di richiesta vengono configurati tramite i metodi di estensione Run, Map e Use. È possibile specificare un singolo delegato di richiesta inline come metodo anonimo (chiamato middleware inline) o definirlo in una classe riutilizzabile. Le classi riutilizzabili o i metodi anonimi inline costituiscono il middleware o sono noti anche come componenti middleware. Ogni componente middleware è responsabile della chiamata del componente seguente nella pipeline o del corto circuito della pipeline. Quando un middleware esegue un corto circuito, viene denominato middleware terminale perché impedisce all'ulteriore middleware di elaborare la richiesta.

In Eseguire la migrazione di moduli HTTP in middleware vengono spiegate le differenze tra le pipeline delle richieste in ASP.NET Core e in ASP.NET 4.x e sono riportati altri esempi di middleware.

Creare una pipeline middleware con IApplicationBuilder

La pipeline delle richieste ASP.NET Core è costituita da una sequenza di delegati di richiesta, chiamati uno dopo l'altro. Il diagramma seguente illustra il concetto. Il thread di esecuzione seguente le frecce nere.

Modello di elaborazione delle richieste che visualizza una richiesta in arrivo, l'elaborazione tramite tre middleware e la risposta che esce dall'app. Ogni middleware esegue la relativa logica e passa la richiesta al middleware successivo in corrispondenza dell'istruzione next(). Dopo che il terzo middleware ha elaborato la richiesta, la richiesta torna ai due middleware precedenti in ordine inverso per un'ulteriore elaborazione dopo le istruzioni next() prima di uscire dall'app sotto forma di risposta al client.

I delegati possono eseguire le operazioni prima del delegato successivo e dopo di esso. I delegati che gestiscono le eccezioni devono essere chiamati nella prima parte della pipeline in modo che possano individuare le eccezioni che si verificano nelle parti successive della pipeline.

L'app di ASP.NET Core più semplice imposta un delegato di richiesta singolo che gestisce tutte le richieste. In questo caso non è inclusa una pipeline di richieste effettiva. Al contrario, viene chiamata una singola funzione anonima in risposta a ogni richiesta HTTP.

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}

È possibile concatenare più delegati di richiesta insieme con Use. Il parametro next rappresenta il delegato successivo nella pipeline. È possibile eseguire il corto circuito della pipeline non chiamando il parametro next. In genere è possibile eseguire un'azione prima e dopo il delegato successivo, come illustra l'esempio seguente:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

Quando un delegato non passa una richiesta al delegato successivo, si crea un cosiddetto corto circuito della pipeline delle richieste. Il corto circuito è spesso opportuno poiché evita l'esecuzione di operazioni non necessarie. Ad esempio, il middleware dei file statici può operare come middleware terminale elaborando una richiesta di un file statico ed effettuando il corto circuito della pipeline rimanente. Il middleware aggiunto alla pipeline prima del middleware che termina l'ulteriore elaborazione elabora comunque il codice dopo le istruzioni next.Invoke. Vedere comunque l'avviso seguente sul tentativo di scrivere una risposta che è già stata inviata.

Avviso

Non chiamare next.Invoke dopo aver inviato la risposta al client. Le modifiche apportate a HttpResponse dopo l'avvio della risposta generano un'eccezione. Ad esempio, l'impostazione delle intestazioni e di un codice di stato generano un'eccezione. Scrivere nel corpo della risposta dopo aver chiamato next:

  • Può causare una violazione del protocollo. Ad esempio, scrivere un contenuto che supera il valore Content-Length specificato.
  • Può danneggiare il formato del corpo. Ad esempio, scrivere un piè di pagina HTML in un file CSS.

HasStarted è un hint utile per indicare se le intestazioni sono state inviate o se è stato scritto contenuto nel corpo.

I delegati Run non ricevono un parametro next. Il primo delegato Run è sempre terminale e termina la pipeline. Run è una convenzione. Alcuni componenti del middleware possono esporre metodi Run[Middleware] che vengono eseguiti al termine della pipeline:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

Per visualizzare i commenti del codice tradotti in lingue diverse dall'inglese, segnalarlo in questo problema di discussione su GitHub.

Nell'esempio precedente il delegato Run scrive "Hello from 2nd delegate." nella risposta e quindi termina la pipeline. Se un altro delegato Use o Run viene aggiunto dopo il delegato Run, non viene chiamato.

Ordine del middleware

Il diagramma seguente illustra la pipeline di elaborazione completa delle richieste per app ASP.NET Core MVC e Razor Pages. È possibile vedere come, in un'app tipica, vengono ordinati i middleware esistenti e dove vengono aggiunti i middleware personalizzati. Si ha il controllo completo su come riordinare i middleware esistenti o inserire nuovi middleware personalizzati in base alle esigenze per gli specifici scenari.

Pipeline del middleware di ASP.NET Core

Il middleware Endpoint nel diagramma precedente esegue la pipeline di filtro per il tipo di app corrispondente, MVC o Razor Pages.

Pipeline di filtro di ASP.NET Core

L'ordine in cui vengono aggiunti i componenti middleware nel metodo Startup.Configure definisce l'ordine in cui i componenti middleware vengono richiamati per le richieste e l'ordine inverso per la risposta. Questo ordinamento è fondamentale per la sicurezza, le prestazioni e la funzionalità.

Il metodo Startup.Configure seguente aggiunge componenti middleware correlati alla sicurezza nell'ordine consigliato tipico:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    // app.UseCookiePolicy();

    app.UseRouting();
    // app.UseRequestLocalization();
    // app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();
    // app.UseSession();
    // app.UseResponseCompression();
    // app.UseResponseCaching();

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

Nel codice precedente:

  • Il middleware che non viene aggiunto durante la creazione di una nuova app Web con singoli account utente viene impostato come commento.
  • Non tutti i middleware vengono visualizzati in questo ordine esatto, ma molti lo fanno. Ad esempio:
    • UseCors, UseAuthentication e UseAuthorization devono essere visualizzati nell'ordine visualizzato.
    • UseCors attualmente deve precedere UseResponseCaching a causa di questo bug.
    • UseRequestLocalization deve comparire prima di qualsiasi middleware che potrebbe controllare le impostazioni cultura della richiesta (ad esempio, app.UseMvcWithDefaultRoute()).

In alcuni scenari, il middleware ha un ordinamento diverso. Ad esempio, l'ordinamento della memorizzazione nella cache e della compressione è specifico dello scenario e sono disponibili più ordinamenti validi. Ad esempio:

app.UseResponseCaching();
app.UseResponseCompression();

Con il codice precedente, si potrebbe risparmiare CPU memorizzando nella cache la risposta compressa, ma ci si potrebbe ritrovare a memorizzare nella cache più rappresentazioni di una risorsa usando algoritmi di compressione diversi, ad esempio Gzip o Brotli.

L'ordinamento seguente combina i file statici per consentire la memorizzazione nella cache di file statici compressi:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

Il metodo Startup.Configure seguente aggiunge componenti del middleware per gli scenari di app comuni:

  1. Gestione errori/eccezioni
    • Quando l'app viene eseguita nell'ambiente di sviluppo:
      • Il middleware della pagina delle eccezioni per gli sviluppatori (UseDeveloperExceptionPage) segnala gli errori di runtime delle app.
      • Il middleware della pagina degli errori del database segnala gli errori di runtime del database.
    • Quando l'app viene eseguita nell'ambiente di produzione:
      • Il middleware del gestore delle eccezioni (UseExceptionHandler) intercetta le eccezioni generate nei middleware seguenti.
      • Il middleware del protocollo HSTS (HTTP Strict Transport Security) (UseHsts) aggiunge l'intestazione Strict-Transport-Security.
  2. Il middleware di reindirizzamento HTTPS (UseHttpsRedirection) reindirizza le richieste HTTP a HTTPS.
  3. Il middleware dei file statici (UseStaticFiles) restituisce i file statici e impedisce ulteriori elaborazioni della richiesta.
  4. Il middleware dei criteri per i Cookie (UseCookiePolicy) rende l'app conforme al Regolamento generale sulla protezione dei dati (GDPR).
  5. Il middleware di routing (UseRouting) instrada le richieste.
  6. Il middleware di autenticazione (UseAuthentication) tenta di autenticare l'utente prima che sia autorizzato ad accedere a risorse protette.
  7. Il middleware di autorizzazione (UseAuthorization) autorizza un utente ad accedere alle risorse protette.
  8. Il middleware di sessione (UseSession) stabilisce e mantiene aggiornato lo stato sessione. Se l'app usa lo stato sessione, chiamare il middleware di sessione dopo il middleware dei criteri per i Cookie e prima del middleware MVC.
  9. Middleware di routing degli endpoint (UseEndpoints con MapRazorPages) per aggiungere endpoint Razor Pages alla pipeline di richiesta.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Nell'esempio di codice precedente, ogni metodo di estensione del middleware viene esposto in IApplicationBuilder tramite lo spazio dei nomi Microsoft.AspNetCore.Builder.

UseExceptionHandler è il primo componente middleware aggiunto alla pipeline. Pertanto, il middleware del gestore di eccezioni intercetta le eccezioni che si verificano nelle chiamate successive.

Il middleware dei file statici viene chiamato nella prima parte della pipeline in moda che possa gestire le richieste ed eseguire un corto circuito senza passare attraverso i componenti rimanenti. Il middleware dei file statici non offre controlli di autorizzazione. I file serviti dal middleware per i file statici, inclusi i file in wwwroot, sono disponibili pubblicamente. Per informazioni su un approccio alla protezione dei file statici, vedere File statici in ASP.NET Core.

Se la richiesta non è gestita dal middleware dei file statici, viene passata al middleware di autenticazione (UseAuthentication) che esegue l'autenticazione. L'autenticazione non esegue il corto circuito di richieste non autenticate. Sebbene il middleware di autenticazione esegua l'autenticazione delle richieste, l'autorizzazione (e il rifiuto) si verifica solo dopo che MVC seleziona una pagina Razor o un controller MVC specifico e un'azione.

L'esempio seguente illustra un ordinamento del middleware nel quale le richieste dei file statici vengono gestite dal middleware dei file statici prima del middleware di compressione delle risposte. I file statici non vengono compressi con questo ordine di middleware. Le risposte Razor Pages possono essere compresse.

public void Configure(IApplicationBuilder app)
{
    // Static files aren't compressed by Static File Middleware.
    app.UseStaticFiles();

    app.UseRouting();

    app.UseResponseCompression();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Per le applicazioni a pagina singola, il middleware delle applicazioni a pagina singola UseSpaStaticFiles è in genere l'ultimo nella pipeline del middleware. Il middleware delle applicazioni a pagina singola è l'ultimo:

  • Per consentire a tutti gli altri middleware di rispondere prima alle richieste corrispondenti.
  • Per consentire l'esecuzione di applicazioni a pagina singola con routing lato client per tutte le route non riconosciute dall'app server.

Per altri dettagli sulle applicazioni a pagina singola, vedere le guide per i modelli di progetto React e Angular.

Ordine del middleware delle intestazioni inoltrate

Il middleware delle intestazioni inoltrate deve essere eseguito prima di altro middleware. Questo ordine garantisce che il middleware basato sulle intestazioni inoltrate possa usare i valori di intestazione per l'elaborazione. Per eseguire il middleware delle intestazioni inoltrate dopo il middleware di diagnostica e gestione degli errori, vedere Ordine del middleware delle intestazioni inoltrate.

Creare un ramo nella pipeline del middleware

Le estensioni Map vengono usate come convenzione per la diramazione della pipeline. Map crea un ramo nella pipeline delle richieste in base alle corrispondenze del percorso della richiesta specificato. Se il percorso della richiesta inizia con il percorso specificato, il ramo viene eseguito.

public class Startup
{
    private static void HandleMapTest1(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 1");
        });
    }

    private static void HandleMapTest2(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 2");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1", HandleMapTest1);

        app.Map("/map2", HandleMapTest2);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
        });
    }
}

La tabella seguente visualizza le richieste e le risposte da http://localhost:1234 usando il codice precedente.

Richiesta Risposta
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

Quando si usa Map, i segmenti di percorso corrispondenti vengono rimossi da HttpRequest.Path e aggiunti a HttpRequest.PathBase per ogni richiesta.

Map supporta l'annidamento, ad esempio:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map può anche trovare la corrispondenza di più segmenti contemporaneamente:

public class Startup
{
    private static void HandleMultiSeg(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map multiple segments.");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1/seg1", HandleMultiSeg);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

MapWhen crea un ramo nella pipeline delle richieste in base al risultato del predicato specificato. È possibile usare qualsiasi predicato di tipo Func<HttpContext, bool> per mappare le richieste a un nuovo ramo della pipeline. Nell'esempio seguente viene usato un predicato per rilevare la presenza di una variabile di stringa di query branch:

public class Startup
{
    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            var branchVer = context.Request.Query["branch"];
            await context.Response.WriteAsync($"Branch used = {branchVer}");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
                               HandleBranch);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
        });
    }
}

La tabella seguente visualizza le richieste e le risposte da http://localhost:1234 usando il codice precedente:

Richiesta Risposta
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Ramo usato = main

UseWhen crea anche un ramo nella pipeline delle richieste in base al risultato del predicato specificato. Diversamente da MapWhen, questo ramo viene ricongiunto alla pipeline principale se non esegue il corto circuito o contiene un middleware terminale:

public class Startup
{
    private void HandleBranchAndRejoin(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.Use(async (context, next) =>
        {
            var branchVer = context.Request.Query["branch"];
            logger.LogInformation("Branch used = {branchVer}", branchVer);

            // Do work that doesn't write to the Response.
            await next();
            // Do other work that doesn't write to the Response.
        });
    }

    public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                               appBuilder => HandleBranchAndRejoin(appBuilder, logger));

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from main pipeline.");
        });
    }
}

Nell'esempio precedente viene scritta la risposta "Hello from main pipeline" per tutte le richieste. Se la richiesta include una variabile di stringa di query branch, il relativo valore viene registrato prima del ricongiungimento con la pipeline principale.

Middleware incorporato

ASP.NET Core include i componenti middleware seguenti. Nella colonna Ordinamento sono disponibili note sul posizionamento del middleware nella pipeline di elaborazione delle richieste e indicazioni sulle condizioni nelle quali il middleware può terminare l'elaborazione delle richieste. Quando un middleware esegue un corto circuito della pipeline di elaborazione delle richieste e impedisce al middleware a valle di elaborare una richiesta, viene denominato middleware terminale. Per altre informazioni sul corto circuito, vedere la sezione Creare una pipeline middleware con IApplicationBuilder.

Middleware Descrizione Ordine
autenticazione Offre il supporto dell'autenticazione. Prima di HttpContext.User. Terminale per i callback OAuth.
Autorizzazione Fornisce il supporto per l'autorizzazione. Subito dopo il middleware di autenticazione.
Criteri per i Cookie Registra il consenso degli utenti per l'archiviazione delle informazioni personali e applica gli standard minimi per i campi dei cookie, come secure e SameSite. Prima del middleware che emette i cookie. Esempi: Autenticazione, Sessione, MVC (TempData).
CORS Configura la condivisione di risorse tra le origini (CORS). Prima dei componenti che usano CORS. UseCors attualmente deve precedere UseResponseCaching a causa di questo bug.
Diagnostica Diversi middleware separati che forniscono una pagina delle eccezioni per sviluppatori, la gestione delle eccezioni, pagine di codici di stato e la pagina Web predefinita per le nuove app. Prima dei componenti che generano errori. Terminale per le eccezioni o per la gestione della pagina Web predefinita per le nuove app.
Intestazioni inoltrate Inoltra le intestazioni proxy nella richiesta corrente. Prima dei componenti che usano i campi aggiornati. Esempi: schema, host, IP client, metodo.
Controllo integrità Controlla l'integrità di un'app ASP.NET Core e le relative dipendenze, come il controllo della disponibilità del database. Terminale se una richiesta corrisponde a un endpoint di controllo di integrità.
Propagazione delle intestazioni Propaga le intestazioni HTTP dalla richiesta in ingresso alle richieste client HTTP in uscita.
Override del metodo HTTP Consente a una richiesta POST in arrivo di eseguire l'override del metodo. Prima dei componenti che usano il metodo aggiornato.
Reindirizzamento HTTPS Reindirizzare tutte le richieste HTTP a HTTPS. Prima dei componenti che usano l'URL.
Protocollo HTTP Strict Transport Security (HSTS) Middleware di ottimizzazione della sicurezza che aggiunge un'intestazione della risposta speciale. Prima dell'invio delle risposte e dopo i componenti che modificano le richieste. Esempi: intestazioni inoltrate, riscrittura dell'URL.
MVC Elabora le richieste con MVC/Razor Pages. Terminale se una richiesta corrisponde a una route.
OWIN Interoperabilità con app, server e middleware basati su OWIN. Terminale se la richiesta viene elaborata completamente dal middleware OWIN.
Memorizzazione nella cache delle risposte Offre il supporto per la memorizzazione delle risposte nella cache. Prima dei componenti che richiedono la memorizzazione nella cache. UseCORS deve precedere UseResponseCaching.
Compressione delle risposte Offre il supporto per la compressione delle risposte. Prima dei componenti che richiedono la compressione.
Localizzazione della richiesta Offre il supporto per la localizzazione. Prima dei componenti sensibili alla localizzazione. Deve comparire dopo il middleware di routing quando si usa RouteDataRequestCultureProvider.
Routing di endpoint Definisce e vincola le route di richiesta. Terminale per le route corrispondenti.
Applicazione a pagina singola Gestisce tutte le richieste da questo punto nella catena del middleware restituendo la pagina predefinita per l'applicazione a pagina singola In posizione avanzata nella catena, in modo che abbiano la precedenza altri middleware per la gestione di file statici, azioni MVC e così via.
Sessione Offre il supporto per la gestione delle sessioni utente. Prima dei componenti che richiedono la Sessione.
File statici Offre il supporto per la gestione di file statici e l'esplorazione directory. Terminale se una richiesta corrisponde a un file.
URL Rewrite (Riscrittura URL) Offre il supporto per la riscrittura degli URL e il reindirizzamento delle richieste. Prima dei componenti che usano l'URL.
WebSocket Abilita il protocollo WebSocket. Prima dei componenti necessari per accettare le richieste WebSocket.

Risorse aggiuntive