Come abbiamo visto in un precedente script (https://www.aspitalia.com/script/1202/Applicazioni-Web-Basate-OWIN-Selfhosting-ASP.NET-Web-API.aspx), una pipeline OWIN consiste di una sequenza ordinata di middleware che compiono delle elaborazioni su una richiesta web.
Questo layout è sensibilmente diverso da quello tradizionale di ASP.NET, che impiega invece degli HttpModules che sottoscrivono eventi prestabiliti nel ciclo di vita della richiesta.
In una pipeline OWIN, ogni middleware che vi abbiamo aggiunto ha la facoltà di compiere una o più delle seguenti operazioni:
- Leggere i dati della richiesta ed eventualmente riscriverla (si pensi per esempio ad un middleware di URL rewriting);
- Decidere di inoltrare l'esecuzione al middleware immediatamente successivo;
- Produrre l'output della risposta, eventualmente avvelendosi di servizi come ASP.NET Web API;
- Ispezionare ed eventualmente riscrivere la risposta prodotta da altri middleware nella pipeline.
Per implementare un middleware, la specifica OWIN ci chiede semplicemente di creare un application delegate o AppFunc, ovvero una funzione che accetti in ingresso un IDictionary
Formalmente, possiamo implementare il nostro primo middleware come una semplice classe C# contenente un metodo Invoke che abbia tale firma.
//Alias dell'application delegate using AppFunc = Func<IDictionary<string, object>, Task>; public class MyFirstMiddleware { private readonly AppFunc nextMiddleware; public MyFirstMiddleware(AppFunc nextMiddleware) { //Nel costruttore ci viene fornito dall'host //il riferimento al prossimo middleware this.nextMiddleware = nextMiddleware; } public async Task Invoke(IDictionary<string, object> environment) { //Qui compio delle elaborazioni //Poi scelgo di eseguire il prossimo middleware await nextMiddleware.Invoke(environment); } }
Purtroppo è subito evidente che questa implementazione ci costringe a lavorare con un'interfaccia di basso livello. Se nel metodo Invoke volessimo leggere i dati della richiesta, infatti, saremmo obbligati a lavorare con il dizionario di ambiente e quindi anche a conoscere il nome esatto delle sue chiavi, così come descritte dalla specifica OWIN.
Fortunatamente ci sono modi alternativi per implementare un middleware. Microsoft ci mette a disposizione delle astrazioni distribuite come pacchetti NuGet da usare in applicazioni web self-hosted o in applicazioni ASP.NET Core 1.0, di cui ASPItalia si occuperà prossimamente.
Nel pacchetto Microsoft.Owin, ad esempio, troviamo la classe base OwinMiddleware e l'interfaccia IOwinContext che espone le proprietà Request e Response, concettualmente simili a quelle che abbiamo usato in passato nelle applicazioni ASP.NET. Esse agiscono da intermediarie con il dizionario di ambiente sottostante.
Andiamo dunque a riscrivere il nostro middleware in modo che si avvalga di tale pacchetto. Contestualmente, lo rendiamo più interessante dandogli lo scopo di autorizzare la richiesta corrente in base a queste due semplici regole:
- Se il client presenta credenziali corrette, invocherà l'esecuzione del middleware successivo in modo che la richiesta faccia il suo corso normale;
- Altrimenti, il middleware risponderà al client con uno status code di errore ed impedirà che l'elaborazione della richiesta prosegua oltre.
Iniziamo preparando un'applicazione web self-hosted descritto nel precedente script (https://www.aspitalia.com/script/1202/Applicazioni-Web-Basate-OWIN-Selfhosting-ASP.NET-Web-API.aspx) ed aggiungiamo un nuovo file di codice chiamato AuthMiddleware.cs con questo contenuto:
//Ora deriviamo da OwinMiddleware public class AuthMiddleware : OwinMiddleware { private readonly TextWriter logger; private readonly OwinMiddleware nextMiddleware; //Nel costruttore possiamo esprimere dipendenze da altri servizi. //In questo caso, richiediamo un TextWriter che //useremo per loggare dei messaggi. public AuthMiddleware(OwinMiddleware nextMiddleware, TextWriter logger) : base(nextMiddleware) { this.nextMiddleware = nextMiddleware; this.logger = logger; } //Il metodo Invoke accetta ora un oggetto di tipo IOwinContext //e non più il dizionario di ambiente. public override async Task Invoke(IOwinContext context) { //Ora posso accedere alla richiesta in maniera //fortemente tipizzata. var headers = context.Request.Headers; if (headers.ContainsKey("Authorization") && IsAuthValid(headers["Authorization"])) { //Se l'utente è autorizzato, //invoco l'esecuzione del prossimo middleware. await nextMiddleware.Invoke(context); return; } //Altrimenti loggo un messaggio var msg = "Tentativo di accesso non autorizzato"; await logger?.WriteLineAsync(msg); //Poi imposto lo status code "Unauthorized" sulla Response context.Response.StatusCode = 401; //Infine NON chiamo l'Invoke sul prossimo middleware //dato che l'utente non era autorizzato. //L'elaborazione della richiesta non proseguirà oltre. } private bool IsAuthValid(string auth) { //TODO: qui logica di verifica dei dati di autenticazione return true; } }
Ora non resta che aggiungere il nostro middleware alla pipeline OWIN. Per far questo entriamo nella classe Startup e dal suo metodo Configuration invochiamo il metodo Use dell'IAppBuilder. Dato che l'esecuzione dei middleware è seriale, è molto importante che vengano aggiunti nell'ordine corretto. Nel caso specifico, il middleware di autorizzazione dovrà trovarsi prima di quello del routing di ASP.NET Web API, affinché possa bloccare la richiesta prima che la nostra logica di business venga eseguita.
public class Startup { public void Configuration(IAppBuilder appBuilder) { appBuilder.Use(typeof(AuthMiddleware), Console.Out); //Qui registro gli altri middleware //es. il routing di Web API } }
Al metodo Use forniamo il Type del nostro middleware e, opzionalmente, qualsiasi altro oggetto sia richiesto dal costruttore. In questo esempio forniamo Console.Out, un oggetto di tipo TextWriter che verrà usato dal middleware come facility di logging.
Questo esercizio ci è servito a far pratica con la pipeline OWIN, e a inserirci con un middleware che incapsula della logica personalizzata.
Teniamo a mente, però, che alcune delle funzionalità più comuni sono già state implementate da middleware ottenibili da NuGet. Se necessitiamo di autenticare ed autorizzare i client come in questo esercizio, ASP.NET Identity sarà probabilmente la scelta pià indicata.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Persistere la ChatHistory di Semantic Kernel in ASP.NET Core Web API per GPT
Usare i servizi di Azure OpenAI e ChatGPT in ASP.NET Core con Semantic Kernel
Supporto ai tipi DateOnly e TimeOnly in Entity Framework Core
Usare le navigation property in QuickGrid di Blazor
Migliorare i tempi di risposta di GPT tramite lo streaming endpoint in ASP.NET Core
Usare un KeyedService di default in ASP.NET Core 8
Eseguire query manipolando le liste contenute in un oggetto mappato verso una colonna JSON
Utilizzare il trigger SQL con le Azure Function
Creare un'applicazione React e configurare Tailwind CSS
Migliorare la scalabilità delle Azure Function con il Flex Consumption
Utilizzare gRPC su App Service di Azure
I più letti di oggi
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Utilizzare la funzione EF.Parameter per forzare la parametrizzazione di una costante con Entity Framework
- Recuperare App Service cancellati su Azure
- Creare una libreria CSS universale: Immagini
- Eliminare una project wiki di Azure DevOps