To find the answer to this you really have to dive into the source code, so I hope you've got your scuba diving gear because we're going deep.
Step 1
Inside the BotController.cs
file the following piece of code is called:
await Adapter.ProcessAsync(Request, Response, Bot);
which calls the ProcessAsync
method on the IBotFrameworkHttpAdapter
interface.
Step 2
Inside the Startup.cs
file we have the following line:
services.AddSingleton<IBotFrameworkHttpAdapter, BotFrameworkHttpAdapter>();
which says that every time we ask for an IBotFrameworkHttpAdapter
, provide the same instance of BotFrameworkHttpAdapter
- essentially a static variable, you can read more about dependency injection service lifetimes here.
Step 3
Inside the Microsoft.Bot.Builder
package we have the implementation for the ProcessAsync
method, that can for our purposes be reduced to the following line:
var invokeResponse = await ProcessActivityAsync(authHeader, activity, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false);
which calls ProcessActivityAsync
which is another function that lives in this library - the important part here is the bot.OnTurnAsync
parameter being passed in.
Step 5
Also inside the Microsoft.Bot.Builder
package is the implementation for ProcessActivityAsync
:
return await ProcessActivityAsync(claimsIdentity, activity, callback, cancellationToken).ConfigureAwait(false);
which calls an overload of the same method, but before we move on from here, the callback
parameter is the bot.OnTurnAsync
parameter that was passed through before.
Step 6
The overload of ProcessActivityAsync
is also implemented inside the Microsoft.Bot.Builder
package, and can be simplified to this line:
await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false);
where callback
is bot.OnTurnAsync
.
Step 7
Digging deeper still we find the implementation of the RunPipelineAsync
method inside of the Microsoft.Bot.Builder
package which is were things start to get a bit fuzzy... Theoretically we want to fall through to the else
block where the callback
(i.e. bot.OnTurnAsync
) is called:
// Call any registered Middleware Components looking for ReceiveActivityAsync()
if (turnContext.Activity != null)
{
// Other code
}
else
{
// call back to caller on proactive case
if (callback != null)
{
await callback(turnContext, cancellationToken).ConfigureAwait(false);
}
}
However, back in Step 6 we also had this line:
using (var context = new TurnContext(this, activity))
where the context is created, and the activity property is initialised. This same context
is pass through to the RunPipelineAsync
call, which means that we will not fall through to the else block...
But there are the following comment on the RunPipelineAsync
method:
/// <param name="callback">A callback method to run at the end of the pipeline.</param>
and inside the remarks
section:
...Once control reaches the end of the pipeline, the adapter calls
the <paramref name="callback"/> method...
So I believe is it safe to say that our callback
method is being executed which means that we continue by bubbling back up the chain to resolve the function that callback
maps to (bot.OnTurnAsync
).
Step 8
In BotController
we pass in an instance of IBot
to the ProcessAsync
method, and in Startup
we wire up all requests for an IBot
to return an instance of EchoBot
like so:
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, EchoBot>();
The EchoBot
implementation inherits from the ActivityHandler
class:
public class EchoBot : ActivityHandler
Step 9
The ActivityHandler
class provides a default implementation for the OnTurnAsync
method which I will simplify to:
switch (turnContext.Activity.Type)
{
case ActivityTypes.Message:
return OnMessageActivityAsync(new DelegatingTurnContext<IMessageActivity>(turnContext), cancellationToken);
// Other cases
}
which the OnMessageActivityAsync
method on the same class that has an implementation that returns a completed task, i.e. is a no-op, however it is a virtual method - classes which inherit ActivityHandler
can provide their own implementation.
Step 10
A custom implementation for OnMessageActivityAsync
is provided inside the EchoBot
class:
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text}"), cancellationToken);
}
where the user's input is echoed back to them, and thus our journey ends.
Regarding Step 7, the Microsoft team are pretty active on SO for things tagged with botframework
so it might be best to get @mdrichardson or @tdurnford to clarify what happens here.
As an aside in Visual Studio you can debug some library code by enabling the following option:
- Tools --> Options --> Debugger
- Uncheck "Enable Just my Code"
Also if you enable navigation to decompiled sources (you will have to accept a legal notification popup) by doing this:
- Tools --> Options --> Text Editor --> C# --> Advanced
- Check "Enable navigation to decompiled sources"
You will be able to inspect the source code of external packages within Visual Studio itself.