I am talking here based on the latest bits which are available on
Codeplex ASP.NET Web Stack repo.
The order is controlled by the user and there is no arbitrary order here. Let me explain:
Let's say we have two message handlers: MyMessageHandler and MyMessageHandler2. Assuming that we register them as below:
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());
}
What you expect here is for the MyMessageHandler to run first and MyMessageHandler2 as second one, in other words FIFO.
If we look at a little bit under the hood inside the framework, we will see that Initialize method of the HttpServer instance is invoking CreatePipeline method of System.Net.Http.HttpClientFactory (which was previously known as HttpPipelineFactory.Create method as Ali indicated.) CreatePipeline method accepts two parameters: HttpMessageHandler and IEnumerable<DelegatingHandler>. HttpServer.Initialize method is passing System.Web.Http.Dispatcher.HttpControllerDispatcher for HttpMessageHandler parameter as the last HttpMessageHandler inside the chain and HttpConfiguration.MessageHandlers for IEnumerable<DelegatingHandler> parameter.
What happens inside the CreatePipeline method is very clever IMO:
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;
}
As you can see, the message handler order is reversed and the Matryoshka doll is created but be careful here: it is ensured that HttpControllerDispatcher is the last message handler to run inside the chain.
As for the calling twice issue, it is not actually quite true. The message handler won't be called twice, the continuation method you will provide is going to be, on the other hand. It is up to you to make it happen. If you provide a callback (in other words continuation), your message handlers will be called on the way back to the client with the generated response message you can play with.
For example, let's assume that the following two are the message handlers we have registered above:
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;
});
}
}
And this is the other one:
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;
});
}
}
As we have provided continuation, our message handlers will be called back on the way back to the client in FILO order. So, the continuation method inside the MyMessageHandler2 will be the first one to be invoked on the way back and the one inside the MyMessageHandler will be the second.