DCR: Adapter/Bot initialization is confusing and boundary between middleware and bot is fuzzy
See original GitHub issueFeedback from hackathons shows that there is friction between the way the middleware pipeline is hooked up, how it is consumed from ASP.NET and how your bot logic hooks up to it.
Example of the issue
- Initialization: there is both an adapter and a bot, you have to pass the adapter into the bot, but keep the adapter around so you can push an activity into the adapter which goes to the bot. This really confusing and causes us to describe 2 concepts (bots and adapters) and the complex handshake between them
- state Some adapters need to keep track of stuff for the entire turn, but since they are not middleware and technically don’t know about the bot it creates weird tracking metaphors
- BotLogic we call the middleware class Bot and have .OnReceive() as a piece of middleware, which pushes people into the direction of thinking of the entire thing as a bot. This blurs the boundary between the bot’s logic and stuff which is more environmental like setting up a runtime middleware.
Proposal
This proposal is implemented in pull request #145
-
Rename middleware class from Bot to BotAdapter Renaming the class better describes it’s functionality, which is to create act as abstraction between the bot and the runtime environment and to provide middleware hooks for running a bot in. It also then creates a mental model where you don’t push all sorts of bot logic stuff into the middleware. While you can of course do whatever you want in middleware, the naming makes it a bit more clear that it’s about the pipeline, not the bot itself.
-
Combine Adapter and Bot together using inheritance Instead of having 2 concepts, merge them together into 1 base class called a BotAdapter. (I am open to other names, and like BotHost) To support a new environment you create a class which is derived from the base BotAdapter class which then implements appropriate methods to adapt to communication with the environment. The base class gives you the standard pipeline implementation.
All BotAdapters would have at least 4 public methods
- .Use() for registering middleware using fluent pattern
- ProcessActivity(callback) which has appropriate parameters for environment but then calls the bot logic
- CreateConversation() and ContinueConversation() which allows programs to do proactive scenarios program to create a new conversation
- Remove .OnReceive() don’t use .OnReceive() as middleware, but instead pass bot’s logic as a simple (BotContext) callback
Sample
The net result in a ASP.NET Core project is that it looks like this: Startup.cs
// define BotAdapter as singleton
services.AddSingleton<BotFrameworkAdapter>(serviceProvider =>
{
return new BotFrameworkAdapter(Configuration)
.Use(new BotStateManager(new MemoryStorage()));
});
MessagesController.cs
[Route("api/[controller]")]
public class MessagesController : Controller
{
public BotFrameworkAdapter botAdapter = null;
public MessagesController(BotFrameworkAdapter botAdapter)
{
this.botAdapter = botAdapter;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]Activity activity)
{
try
{
await botAdapter.ProcessActivity(this.Request.Headers["Authorization"].FirstOrDefault(),
activity,
async (context) =>
{
if (context.Request.Type == ActivityTypes.Message)
{
context.Reply($"echo: {context.Request.AsMessageActivity().Text}");
}
});
return this.Ok();
}
catch (UnauthorizedAccessException)
{
return this.Unauthorized();
}
}
}
Here’s console adapter sample
class Program
{
static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
Console.WriteLine("Welcome to the EchoBot.");
var adapter = new ConsoleAdapter()
.Use(new BotStateManager(new MemoryStorage()));
await adapter.ProcessActivity(async (context) =>
{
if (context.Request.Type == ActivityTypes.Message)
{
context.Reply($"echo: {context.Request.AsMessageActivity().Text}");
}
});
}
}
NOTE: This DCR originally was coded using BotServer as base class. That led to anems like BotFrameworkBotServer and didn’t really capture what was going on. Based on feedback from Ccastro I changed this to BotAdapter and everything fell into place.
Update2: Added console sample
Issue Analytics
- State:
- Created 6 years ago
- Reactions:3
- Comments:13 (9 by maintainers)
Top GitHub Comments
I really like that proposal for nuget pkgs. Not only does it showcase the versatility of the framework simply by showing up, but the implementations will provide a tailored experience. There’s also precedent w/ the Microsoft.AspNet/AspNetCore pkgs.
Just need to put emphasis on the pkg descriptions to lead developers to success.
The Adapter patterns we are showing here are platform agnostic, in that there is no dependency on a given Http framework like ASP.NET/Core
A BotController for ASP.NET/Core which looks like
is easy super easy enough to add, but it does introduce a ASP.NET layer that hides that it’s the same framework across all languages. We have in the past taken the tack of not adding layers which hide stuff (which I am rewriting the AlarmBot) so that it is transparent what is going on.
I could entertain the idea of