ASP.NET Core – Come aggiungere la propria funzione middleware

ASP.NET Core – Come aggiungere la propria funzione middleware

Le funzioni middleware hanno accesso alle richieste prima che vengano inviate ai controller. Allo stesso modo, hanno accesso alle risposte dei controller prima che vengano restituite al client.

Ciò è utile per eseguire operazioni come la registrazione della richiesta e della risposta, la generazione di statistiche sulle richieste, la gestione delle eccezioni e molti altri scenari.

In questo articolo, per prima cosa mostrerò due modi per aggiungere il tuo middleware (classe o inline), quindi parlerò di maggiori dettagli sulle funzioni del middleware.

Opzione 1 – Aggiungi classe middleware

Per aggiungere la tua funzione middleware, prima aggiungi una classe middleware. Il costruttore deve accettare un parametro RequestDelegate ed è necessario aggiungere un metodo che accetti un parametro HttpContext. Nota:questo esempio non riguarda l'inserimento di dipendenze nel middleware.

Nel corpo della funzione middleware, puoi esaminare la richiesta, eseguire la successiva funzione middleware e quindi esaminare la risposta.

public class StatsLoggerMiddleware
{
	private readonly RequestDelegate NextMiddleware;

	public StatsLoggerMiddleware(RequestDelegate nextMiddleware)
	{
		NextMiddleware = nextMiddleware;
	}

	public async Task InvokeAsync(HttpContext context)
	{
		//1 - Inspect the request
		if (context.Request.Headers.ContainsKey("Debug"))
		{
			Console.WriteLine($"Got request. Method={context.Request.Method} Path={context.Request.Path}");

			var sw = Stopwatch.StartNew();

			//2 - Call the next middleware
			await NextMiddleware(context);

			//3 - Inspect the response
			sw.Stop();
			Console.WriteLine($"Request finished. Method={context.Request.Method} Path={context.Request.Path} StatusCode={context.Response.StatusCode} ElapsedMilliseconds={sw.ElapsedMilliseconds}");
		}
	}
}
Code language: C# (cs)

Nota:questo utilizza l'approccio di attivazione del middleware basato su convenzioni, invece dell'approccio di fabbrica.

Quindi in Startup.Configure(), registra la tua classe middleware chiamando app.UseMiddleware():

public class Startup
{
	//rest of class
	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		app.UseMiddleware<StatsLoggerMiddleware>();

		//rest of method
	}
}
Code language: C# (cs)

Ora, quando arrivano le richieste, verrà chiamata la tua funzione middleware.

Ecco un esempio di ciò che questa funzione middleware genera quando arrivano più richieste:

Got request. Method=POST Path=/Stocks/
Request finished. Method=POST Path=/Stocks/ StatusCode=400 ElapsedMilliseconds=180
Got request. Method=POST Path=/Stocks/
Request finished. Method=POST Path=/Stocks/ StatusCode=200 ElapsedMilliseconds=15
Got request. Method=GET Path=/Stocks/
Request finished. Method=GET Path=/Stocks/ StatusCode=405 ElapsedMilliseconds=0
Got request. Method=GET Path=/Stocks/1
Request finished. Method=GET Path=/Stocks/1 StatusCode=200 ElapsedMilliseconds=16Code language: plaintext (plaintext)

Possono essere presenti più funzioni middleware nella pipeline della richiesta. La tua funzione middleware è responsabile del passaggio dell'esecuzione alla successiva funzione middleware nella pipeline. Per farlo, chiama il passato in RequestDelegate. Questo è ciò che attendono NextMiddleware(context) la linea sta facendo. Dopo che la chiamata è tornata, significa che la risposta sta tornando attraverso la pipeline e la tua funzione middleware ha la possibilità di fare qualcosa con la risposta.

Opzione 2:aggiungi middleware in linea

Invece di aggiungere una classe middleware per contenere la funzione middleware, un approccio alternativo consiste nel dichiarare la propria funzione middleware direttamente in Startup.Configure():

public class Startup
{
	//rest of class
	
	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		app.Use(async (context, NextMiddleware) =>
		{
			//1 - Operate on the request
			if (context.Request.Headers.ContainsKey("Debug"))
			{
				Console.WriteLine($"Got request. Method={context.Request.Method} Path={context.Request.Path}");

				var sw = Stopwatch.StartNew();

				//2 - Call the next middleware
				await NextMiddleware();

				//3 - Operate on the response
				sw.Stop();
				Console.WriteLine($"Request finished. Method={context.Request.Method} Path={context.Request.Path} StatusCode={context.Response.StatusCode} ElapsedMilliseconds={sw.ElapsedMilliseconds}");
			}
		});

		//rest of method
	}
}
Code language: C# (cs)

Questa viene definita funzione middleware in linea. Un problema con questo approccio è che non è possibile eseguire il test unitario della funzione middleware. Potresti voler utilizzare solo le funzioni del middleware in linea per scenari molto semplici.

Le funzioni del middleware vengono richiamate nell'ordine in cui sono registrate

Quando chiami app.UseMiddleware(), stai registrando le funzioni del middleware. L'ordine in cui li registri determina la loro posizione nella pipeline delle richieste.

Supponiamo che tu abbia tre classi middleware – FirstMiddleware, SecondMiddleware e ThirdMiddleware – e le registri come segue:

public class Startup
{
	//rest of class

	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		app.UseMiddleware<FirstMiddleware>();
		app.UseMiddleware<SecondMiddleware>();
		app.UseMiddleware<ThirdMiddleware>();

		//rest of method
	}
}
Code language: C# (cs)

Queste classi middleware registrano semplicemente di aver ricevuto la richiesta e la risposta. Quando arriva una richiesta, ecco l'output:

FirstMiddleware got request. Calling next middleware.
SecondMiddleware got request. Calling next middleware.
ThirdMiddleware got request. Calling next middleware.
ThirdMiddleware got response
SecondMiddleware got response
FirstMiddleware got responseCode language: plaintext (plaintext)

Questo mostra l'ordine di esecuzione. Le funzioni middleware vengono eseguite nell'ordine in cui sono registrate in Startup.Configure(). Tienilo a mente quando hai a che fare con più funzioni middleware.

Modifica le intestazioni delle risposte

Quando vuoi modificare le intestazioni delle risposte, devi usare context.Response.OnStarting() e devi configurarlo prima di chiamare RequestDelegate, in questo modo:

public class StatsAppenderMiddleware
{
	private readonly RequestDelegate NextMiddleware;

	public StatsAppenderMiddleware(RequestDelegate nextMiddleware)
	{
		NextMiddleware = nextMiddleware;
	}

	public async Task InvokeAsync(HttpContext context)
	{
		var sw = Stopwatch.StartNew();
		
		//Modify the response headers before calling the next middleware
		context.Response.OnStarting(() =>
		{
			sw.Stop();
			context.Response.Headers.Add("Stats", sw.ElapsedMilliseconds.ToString());
			return Task.CompletedTask;
		});
		
		await NextMiddleware(context);
	}
}
Code language: C# (cs)

Avviso context.Response.OnStarting() è stato configurato prima chiamando la prossima funzione middleware sulla riga 22.

Questa funzione middleware aggiungerà un'intestazione di risposta con il tempo di esecuzione trascorso in millisecondi:

Stats=155Code language: plaintext (plaintext)