Question

Is it possible to control the execution order of custom message handlers?

As an example, I might want a logging handler to execute first, so I always log a request.

Apart from adding the logging handler last, I'm failing to see how to achieve this.

config.MessageHandlers.Add(new CustomHandlerOne()); 
config.MessageHandlers.Add(new CustomHandlerTwo());
config.MessageHandlers.Add(new LoggingHandler());
Was it helpful?

Solution

The order in which you register the handlers determines when they are called but as Aliostad points out they work in a Russian doll model so the first one in is also called as the last one out and so forth.

The registered handlesr are invoked in a bottom-up fashion in the incoming path and top-down in the outgoing. That is, the last entry is called first for an incoming request message but invoked last for an outgoing response message.

OTHER TIPS

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.

No - AFAIK.

It is the Russian Doll model, with one handler inside another until the last one doing the work. This is built in internal class HttpPipelineFactory (you can view the source code as it was released):

    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;
    }

So what the code does is to get a list and then turn it into a Russian Doll.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top