Nella lezione precedente abbiamo parlato della pipeline middleware e dell'EndpointMiddleware
, che sono componenti fondamentali per gestire le richieste HTTP in una applicazione ASP.NET Core. Ora è il momento di approfondire la conoscenza su come unire i vari componenti per costruire la pipeline, ovvero la catena di middleware che viene eseguita per elaborare una richiesta HTTP. La pipeline è uno dei concetti fondamentali di ASP.NET Core, in quanto consente di definire l'ordine in cui i componenti middleware devono essere eseguiti e di gestire la richiesta HTTP in modo flessibile e personalizzato. In questa lezione impareremo a costruire la pipeline e a configurare i middleware in modo da gestire al meglio le richieste HTTP nella nostra applicazione ASP.NET Core.
La costruzione della risposta HTTP mediante middleware
Anzitutto, per capire come avvenga la costruzione della risposta e quindi cosa effettivamente succeda durante i vari passaggi della richiesta tra i componenti del middleware, bisogna introdurre l'oggetto HttpContext
di ASP .NET Core. HttpContext
è uno dei concetti fondamentali di questo framework. Si tratta di un oggetto che rappresenta il contesto di una richiesta HTTP in entrata. L'oggetto contiene informazioni sulla richiesta, come i parametri della query string, i dati del form e le intestazioni HTTP, nonché le risposte generate dal middleware pipeline. Quando una richiesta viene ricevuta dal server, passa attraverso la pipeline del middleware, dove ogni componente può eseguire operazioni sulla richiesta, modificarla o passarla a un altro componente per ulteriori elaborazioni. Il middleware pipeline e l'HttpContext
object sono strettamente correlati. Ogni componente della pipeline accede all'oggetto HttpContext
per accedere alle informazioni sulla richiesta corrente e generare la risposta appropriata. L'HttpContext
object, a sua volta, viene aggiornato con le informazioni elaborate da ogni componente della pipeline.
Come creare la pipeline middleware
Dopo aver spiegato come effettivamente agiscano e cosa facciano i componenti della pipeline middleware, mostriamo come costruirne una per la nostra applicazione. All'interno della directory della nostra applicazione troviamo il file Program.cs
, che è un file fondamentale in ogni applicazione ASP.NET Core. Esso contiene il codice che inizializza l'applicazione e che avvia il server Web che gestirà le richieste HTTP in arrivo. Nella classe Program
, solitamente presente all'interno di questo file, è definito il metodo statico Main
, che costituisce il punto di ingresso dell'applicazione.
Il metodo Main
viene eseguito quando si avvia l'applicazione e ha il compito di creare un'istanza di WebApplication
utilizzando la classe WebApplication.CreateBuilder(args)
, dove args
è un array di stringhe che rappresenta gli eventuali argomenti passati all'applicazione da riga di comando. A partire dall'istanza di WebApplication
, vengono configurati i vari middleware che costituiscono la pipeline di gestione delle richieste HTTP. Infine, viene chiamato il metodo Run()
sull'oggetto WebApplication
per avviare il server Web e iniziare ad ascoltare le richieste HTTP in arrivo sulla porta predefinita dell'applicazione.
In sintesi, il file Program.cs
è il punto di partenza per ogni applicazione ASP.NET Core e contiene il codice necessario per inizializzare l'applicazione e definire la pipeline di gestione delle richieste HTTP. La più semplice applicazione ASP.NET Core ha bisogno di almeno un componente middleware per gestire le sue richieste in entrata. In questo caso non sarebbe necessaria una vera e propria pipeline, perché basterebbe una singola funzione anonima chiamata per rispondere a ogni singola richiesta HTTP. Apriamo il file Program.cs
e scriviamo:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run(async context =>
{
await context.Response.WriteAsync("Hello world!");
});
app.Run();
La funzione rappresenta il minimo indispensabile per creare e avviare un'applicazione Web con ASP.NET Core. La prima riga di codice istanzia un oggetto di tipo WebApplicationBuilder
attraverso il metodo statico CreateBuilder
, a cui si può passare come parametro un array di stringhe args
contenente gli argomenti passati da riga di comando all'applicazione. La seconda riga di codice utilizza il metodo Build()
per creare l'oggetto WebApplication
sulla base della configurazione definita precedentemente. La terza riga di codice definisce il middleware che deve essere eseguito quando viene ricevuta una richiesta HTTP. In questo caso, il middleware è rappresentato dalla lambda function che, una volta invocata, scriverà "Hello world!" nel corpo della risposta HTTP. Infine, la quarta riga di codice chiama il metodo Run()
sull'oggetto WebApplication
per avviare l'applicazione e iniziare ad ascoltare le richieste HTTP in arrivo sulla porta predefinita dell'applicazione. In sintesi, il codice sopra descritto rappresenta il modo più semplice per creare un'applicazione Web ASP.NET Core che risponde con una stringa "Hello world!" a ogni richiesta HTTP ricevuta. La funzione che presentiamo in basso, invece, rappresenta finalmente un esempio di come definire una vera e propria pipeline di middleware in ASP.NET Core.
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();
La terza riga di codice definisce il middleware principale, utilizzando il metodo Use()
per registrare una lambda function che riceve come parametri context
e next
. Il parametro context
rappresenta l'oggetto HttpContext
, che contiene le informazioni sulla richiesta HTTP in arrivo, mentre il parametro next
rappresenta la funzione che deve essere chiamata per eseguire il middleware successivo nella pipeline. All'interno della lambda function vengono eseguite le operazioni relative al middleware principale, seguite dalla chiamata a next.Invoke()
per eseguire il middleware successivo nella pipeline. La quarta riga di codice definisce un altro middleware, utilizzando il metodo Run()
. Questo middleware scriverà "Hello from 2nd delegate." nel corpo della risposta HTTP. Infine, la quinta riga di codice chiama il metodo Run()
sull'oggetto WebApplication
per avviare l'applicazione e iniziare ad ascoltare le richieste HTTP in arrivo sulla porta predefinita dell'applicazione.
In generale, il codice descritto mostra come definire una pipeline di middleware in cui il middleware principale esegue le operazioni relative alla risposta HTTP. I middleware successivi eseguono operazioni che non scrivono direttamente sulla risposta HTTP, come il logging.
Ordine della pipeline
Una cosa importante da tenere a mente è l'ordine in cui i componenti middleware sono aggiunti nel file Program.cs
. Esso definisce l'ordine in cui i componenti middleware vengono invocati sulle richieste e l'ordine inverso per la risposta. L'ordine è critico per la sicurezza, le prestazioni e la funzionalità dell'applicazione. Un esempio di quanto detto ci viene dato da questo codice:
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();
In primo luogo, viene verificato se l'applicazione è in modalità sviluppo attraverso il metodo IsDevelopment()
dell'oggetto env
(che rappresenta l'ambiente di esecuzione dell'applicazione). Se l'applicazione è in modalità sviluppo, vengono aggiunti due middleware per gestire le eccezioni: UseDeveloperExceptionPage()
e UseDatabaseErrorPage()
. Questi middleware forniscono informazioni dettagliate sugli errori che si verificano durante lo sviluppo dell'applicazione, semplificando il debugging.
Se l'applicazione non è in modalità sviluppo, viene invece aggiunto il middleware UseExceptionHandler("/Error")
, che fornisce una pagina di errore generica per gli utenti finali e inoltra le eccezioni non gestite a questa pagina. Successivamente, vengono aggiunti i middleware per la gestione della sicurezza: UseHsts()
abilita la politica di sicurezza HTTP Strict Transport Security, mentre UseHttpsRedirection()
reindirizza tutte le richieste HTTP a HTTPS. Il middleware UseStaticFiles()
consente di servire file statici (ad esempio, fogli di stile CSS, immagini e script JavaScript) dall'applicazione. Il middleware UseCookiePolicy()
consente di definire le impostazioni relative alle politiche dei cookie. Il middleware UseRouting()
abilita il routing delle richieste HTTP e il middleware UseAuthentication()
abilita l'autenticazione degli utenti nell'applicazione.
Il middleware UseAuthorization()
abilita l'autorizzazione degli utenti, ovvero la definizione dei diritti di accesso ai diversi componenti dell'applicazione. Infine, viene aggiunto il middleware UseSession()
, che consente di gestire le sessioni dell'utente, e viene mappata la route per le pagine Razor tramite il metodo MapRazorPages()
. Come detto prima, l'ordine è importante e infatti, il middleware UseHttpsRedirection()
deve essere aggiunto prima di UseStaticFiles()
, in modo che tutte le richieste HTTP vengano reindirizzate ad HTTPS prima che il middleware UseStaticFiles()
possa servire file statici all'utente. Inoltre, i middleware per l'autenticazione e l'autorizzazione (UseAuthentication()
e UseAuthorization()
) devono essere aggiunti dopo UseRouting()
, in modo che la pipeline possa determinare a quale endpoint l'utente sta tentando di accedere prima di richiedere le credenziali di autenticazione.
Infine, il middleware UseSession()
deve essere aggiunto dopo UseRouting()
e UseCookiePolicy()
, in modo che la pipeline possa utilizzare i cookie per archiviare l'ID di sessione dell'utente.
Conclusioni
In questa lezione abbiamo imparato l'importanza della pipeline dei middleware in ASP.NET Core e come essa influisce sulla funzionalità dell'applicazione. Abbiamo visto come l'ordine in cui i middleware sono aggiunti è fondamentale per garantire il corretto funzionamento dell'applicazione e soddisfare le esigenze di sicurezza e prestazioni. Inoltre, abbiamo approfondito la conoscenza dell'oggetto HttpContext
e come questo viene passato da un middleware all'altro all'interno della pipeline. La pipeline dei middleware è uno strumento potente per la gestione delle richieste in ASP.NET Core, e sapere come funziona è fondamentale per la creazione di applicazioni Web sicure, performanti e scalabili.