Scrivere il primo middleware OWIN per ASP.NET

di Moreno Gentili, in ASP.NET MVC, ASP.NET Web API,

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 (in cui sono conservate le informazioni della richiesta web - e della relativa risposta) e che restituisca un Task (http://owin.org/spec/spec/owin-1.0.0.html#_3._Request_Execution).

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

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi