2012-04-20 16 views
5

Ist es möglich, die Ausführungsreihenfolge von benutzerdefinierten Nachrichtenhandlern zu steuern?ASP.NET-Web-API-Nachrichtenhandler

Als Beispiel möchte ich möglicherweise einen Logging-Handler zuerst ausführen, also protokolliere ich immer eine Anfrage.

Abgesehen davon, dass ich zuletzt den Logging-Handler hinzugefügt habe, kann ich nicht erkennen, wie dies erreicht werden kann.

config.MessageHandlers.Add(new CustomHandlerOne()); 
config.MessageHandlers.Add(new CustomHandlerTwo()); 
config.MessageHandlers.Add(new LoggingHandler()); 

Antwort

7

Die Reihenfolge, in der Sie die Handler registrieren, bestimmt, wann sie aufgerufen werden, aber wie Aliostad darauf hinweist, dass sie in einem russischen Puppenmodell funktionieren, wird das erste auch als das letzte und so weiter bezeichnet.

Die registrierten Handles werden im eingehenden Pfad von unten nach oben und im ausgehenden von oben nach unten aufgerufen. Das heißt, der letzte Eintrag wird zuerst für eine eingehende Anforderungsnachricht aufgerufen, aber zuletzt für eine ausgehende Antwortnachricht aufgerufen.

3

Nein - AFAIK.

Es ist das russische Doll-Modell, mit einem Handler in einem anderen, bis der letzte die Arbeit macht. Dies wird in der internen Klasse gebaut HttpPipelineFactory (können Sie den Quellcode als es veröffentlicht wurde):

public static HttpMessageHandler Create(IEnumerable<DelegatingHandler> handlers, HttpMessageHandler innerChannel) 
    { 
     if (innerChannel == null) 
     { 
      throw Error.ArgumentNull("innerChannel"); 
     } 

     if (handlers == null) 
     { 
      return innerChannel; 
     } 

     // Wire handlers up 
     HttpMessageHandler pipeline = innerChannel; 
     foreach (DelegatingHandler handler in handlers) 
     { 
      if (handler == null) 
      { 
       throw Error.Argument("handlers", SRResources.DelegatingHandlerArrayContainsNullItem, typeof(DelegatingHandler).Name); 
      } 

      if (handler.InnerHandler != null) 
      { 
       throw Error.Argument("handlers", SRResources.DelegatingHandlerArrayHasNonNullInnerHandler, typeof(DelegatingHandler).Name, "InnerHandler", handler.GetType().Name); 
      } 

      handler.InnerHandler = pipeline; 
      pipeline = handler; 
     } 

     return pipeline; 
    } 

Also, was der Code tut, ist, eine Liste zu bekommen und es dann wiederum in eine russischen Puppe.

+0

Das habe ich mir gedacht. Ich glaube nicht, dass es auch das Konzept der Anbieter unterstützt. Wie die IFilterprovider-Schnittstelle in MVC. – Darren

+0

Ich stimme zu, dass das 'Async/ContinueWith'-Material das Gefühl vermittelt, Puppen zu verschachteln. Es ist meiner Meinung nach genauer, sie wie bestellt zu beschreiben, wie MS es tut. Jeder Behandler wird zweimal aufgerufen - einmal auf dem Weg in der Reihenfolge und einmal in umgekehrter Reihenfolge. Das dritte Diagramm im folgenden Artikel macht es klar .. http://www.asp.net/web-api/overview/working-with-http/http-message-handlers – EBarr

3

Ich spreche hier auf der Grundlage der neuesten Bits, die auf Codeplex ASP.NET Web Stack Repo verfügbar sind.

Die Bestellung wird vom Benutzer gesteuert und es gibt keine willkürliche Reihenfolge hier. Lassen Sie mich erklären:

Nehmen wir an, wir haben zwei Nachrichtenhandler: MyMessageHandler und MyMessageHandler2. Unter der Annahme, dass wir registrieren sie wie folgt:

protected void Application_Start(object sender, EventArgs e) { 

    RouteConfig.RegisterRoutes(GlobalConfiguration.Configuration.Routes); 
    GlobalConfiguration.Configuration.MessageHandlers.Add(new MyMessageHandler()); 
    GlobalConfiguration.Configuration.MessageHandlers.Add(new MyMessageHandler2()); 
} 

Was man hier erwarten ist, dass die MyMessageHandler zum ersten und MyMessageHandler2 als zweiten läuft, in anderen Worten FIFO.

Wenn wir ein wenig unter der Haube im Inneren des Rahmens betrachten, werden wir sehen, dass Initialize Methode der HttpServer Instanz CreatePipeline Methode von System.Net.Http.HttpClientFactory aufruft (die als HttpPipelineFactory.Create Methode bisher bekannt als Ali angegeben.) CreatePipeline Methode akzeptiert zwei Parameter: HttpMessageHandler und IEnumerable<DelegatingHandler>. HttpServer.Initialize Methode wird System.Web.Http.Dispatcher.HttpControllerDispatcher für HttpMessageHandler Parameter als letzte HttpMessageHandler innerhalb der Kette und HttpConfiguration.MessageHandlers für IEnumerable<DelegatingHandler> Parameter übergeben.

Was im Inneren des CreatePipeline Methode geschieht, ist IMO sehr clever:

public static HttpMessageHandler CreatePipeline(HttpMessageHandler innerHandler, IEnumerable<DelegatingHandler> handlers) 
{ 
    if (innerHandler == null) 
    { 
     throw Error.ArgumentNull("innerHandler"); 
    } 

    if (handlers == null) 
    { 
     return innerHandler; 
    } 

    // Wire handlers up in reverse order starting with the inner handler 
    HttpMessageHandler pipeline = innerHandler; 
    IEnumerable<DelegatingHandler> reversedHandlers = handlers.Reverse(); 
    foreach (DelegatingHandler handler in reversedHandlers) 
    { 
     if (handler == null) 
     { 
      throw Error.Argument("handlers", Properties.Resources.DelegatingHandlerArrayContainsNullItem, typeof(DelegatingHandler).Name); 
     } 

     if (handler.InnerHandler != null) 
     { 
      throw Error.Argument("handlers", Properties.Resources.DelegatingHandlerArrayHasNonNullInnerHandler, typeof(DelegatingHandler).Name, "InnerHandler", handler.GetType().Name); 
     } 

     handler.InnerHandler = pipeline; 
     pipeline = handler; 
    } 

    return pipeline; 
} 

Wie Sie sehen können, die Nachrichten-Handler Reihenfolge umgekehrt wird und die Matryoshka doll erstellt, aber hier vorsichtig sein: es ist sichergestellt, dass HttpControllerDispatcher ist die Letzter Nachrichtenhandler, der innerhalb der Kette ausgeführt wird.

Wie für das zweimal aufrufen Problem ist es eigentlich nicht ganz richtig. Der Message-Handler wird nicht zweimal aufgerufen, sondern die von Ihnen bereitgestellte Fortsetzungs-Methode. Es liegt an Ihnen, dies zu ermöglichen. Wenn Sie einen Rückruf (mit anderen Worten eine Fortsetzung) bereitstellen, werden Ihre Nachrichtenhandler auf dem Weg zurück zum Client mit der generierten Antwortnachricht aufgerufen, mit der Sie spielen können.

Zum Beispiel, nehmen wir an, dass die beiden folgenden die Nachrichtenhandler sind wir oben registriert haben:

public class MyMessageHandler : DelegatingHandler { 

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { 

     //inspect request here 

     return base.SendAsync(request, cancellationToken).ContinueWith(task => { 

      //inspect the generated response 
      var response = task.Result; 

      return response; 
     }); 
    } 
} 

Und das ist die andere:

public class MyMessageHandler2 : DelegatingHandler { 

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { 

     //inspect request here 

     return base.SendAsync(request, cancellationToken).ContinueWith(task => { 

      //inspect the generated response 
      var response = task.Result; 

      return response; 
     }); 
    } 
} 

Wie wir Fortsetzung zur Verfügung gestellt haben, unsere Message-Handler werden auf dem Weg zurück zum Client in FILO-Reihenfolge zurückgerufen. Also wird die Fortsetzungsmethode innerhalb der MyMessageHandler2 die erste sein, die auf dem Weg zurück aufgerufen wird und die innerhalb der MyMessageHandler wird die zweite sein.