Search code examples
c#asp.net.netasp.net-web-apisendasync

base.SendAsync -How symmetrical execution was done?


In asp.net - using Message Handlers - I can customize the request/response by adding message handlers.

So, a request comes in , going through multiple message handlers and then the response is back through the same handlers( in opposite direction).

enter image description here

So, for example : if I attach 2 message handlers : (yes I know, async/await is preferred, but that's from a book)

public class CustomMessageHandler1 : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("CustomMessageHandler1 request invoked");
        return base.SendAsync(request, cancellationToken)
                   .ContinueWith(task =>
                                      {
                                      Debug.WriteLine("CustomMessageHandler1 response invoked");
                                      var response = task.Result;
                                      return response;
                                      });
    }
}


public class CustomMessageHandler2 : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("CustomMessageHandler2 request invoked");
        return base.SendAsync(request, cancellationToken)
                   .ContinueWith(task => 
                                      {
                                      Debug.WriteLine("CustomMessageHandler2 response invoked");
                                      var response = task.Result;
                                      return response;
                                 });
    }
}

Let's not forget to register those in global.asax :

var config = GlobalConfiguration.Configuration;
config.MessageHandlers.Add(new CustomMessageHandler1());
config.MessageHandlers.Add(new CustomMessageHandler2());

And the result is :

enter image description here

As you can see , like I said and like this article says : The execution is symmetrical.

Great.

But then I thought to myself - how did they do that symmetrical execution?

So I succeed creating my own demo of symmetrical execution using continuation:

void Main()
{
 Method1() ;    
}

  public async  Task  Method1 ()
  {
   Console.WriteLine ("Method_1"); //alias to request
   await Method2();
   Console.WriteLine ("Finished Method_1");  //alias to response
  }


 public async Task  Method2 ()
 {
  Console.WriteLine ("Method_2");  //alias to request
  await Task.FromResult("...");//dummy
  Console.WriteLine ("Finished Method_2");  //alias to response

 }

And the result was indeed symetrical :

Method_1
Method_2
Finished Method_2
Finished Method_1

But in my code Method1 called Method2 and that's why it worked !.

But in the first code above - they do NOT call each other ! it's like something is invoking only the first part ( before the ContinueWith) from each method , and then run the second part( after the ContinueWith) from each method.

Something like :

enter image description here

So i've look at the reference source for base.Sendasync : But couldn't find how base.Sendasync is doing this symmetrical execution

Question

How does base.Sendasync is doing that symmetrical execution without having one method calling the other?


Solution

  • Here is the console-appified Web API pipeline for you.

    abstract class BaseHandler // HttpHandler
    {
        public abstract Task MyMethodAsync();
    }
    
    abstract class Handler : BaseHandler // MessageHandler
    {
        public Handler InnerHandler { get; set; }
    
        public override Task MyMethodAsync()
        {
            if (this.InnerHandler != null)
                return this.InnerHandler.MyMethodAsync();
            else
                return Task.FromResult<object>(null);
        }
    }
    
    class Handler1 : Handler
    {
        public override async Task MyMethodAsync()
        {
            Console.WriteLine("Method_1"); //alias to request
            await base.MyMethodAsync();
            Console.WriteLine("Finished Method_1");  //alias to response
        }
    }
    
    class Handler2 : Handler
    {
        public override async Task MyMethodAsync()
        {
            Console.WriteLine("Method_2"); //alias to request
            await base.MyMethodAsync();
            Console.WriteLine("Finished Method_2");  //alias to response
        }
    }
    
    class LastHandler : Handler
    {
        public override async Task MyMethodAsync()
        {
            // Does nothing
            await base.MyMethodAsync();
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            List<Handler> handlers = new List<Handler>();
            // You do this when you add the handler to config
            handlers.Add(new Handler1());
            handlers.Add(new Handler2());
    
            // This part is done by HttpClientFactory
            Handler pipeline = new LastHandler();
    
            handlers.Reverse();
            foreach (var handler in handlers)
            {
                handler.InnerHandler = pipeline;
                pipeline = handler;
            }
    
            pipeline.MyMethodAsync().Wait();
        }
    }