Understand the structure of an echo bot

APPLIES TO: SDK v4

The Bot Framework templates and samples are written for ASP.NET (C#), restify (JavaScript), and aiohttp (Python). However, the web service features are not part of the Bot Framework SDK, but part of the web framework you choose to use.

All bot applications share some common features.

Feature Description
Resource provisioning The bot as a web app needs to create a web service, bot adapter, and bot object.
Messaging endpoint The web app needs to implement a messaging endpoint on which to receive activities and forward activities to the bot adapter.
Bot adapter The adapter receives activities from the messaging endpoint, forwards them to the bot's turn handler, and catches any errors or exceptions the bot's logic doesn't catch.
Bot object The bot object handles the bot's reasoning or logic for the turn.

You can create an echo bot from the templates, as described in the quickstarts (for C#, JavaScript, or Python), or you can copy an echo bot project from the Microsoft/BotBuilder-Samples repository.

The C# and JavaScript templates have built-in support for streaming connections. This article does cover streaming features. For information about streaming connections, see how to connect a bot to Direct Line Speech.

Prerequisites

Bot templates

A bot is a web application, and templates are provided for each language.

The Bot Framework includes both VSIX and dotnet templates.

The templates generate an ASP.NET MVC Core web app. If you look at the ASP.NET fundamentals, you'll see similar code in files such as Program.cs and Startup.cs. These files are required for all web apps and are not bot specific.

Note

The VSIX package includes both .NET Core 2.1 and .NET Core 3.1 versions of the C# templates. When creating new bots in Visual Studio 2019, you should use the .NET Core 3.1 templates. The current bot samples use .NET Core 3.1 templates. You can find the samples that use .NET Core 2.1 templates in the 4.7-archive branch of the BotBuilder-Samples repository. For information about deploying .NET Core 3.1 bots to Azure, see how to deploy your bot to Azure.

The appsettings.json file specifies the configuration information for your bot, such as its app ID, and password among other things. If using certain technologies or using this bot in production, you will need to add your specific keys or URL to this configuration. For this echo bot, however, you don't need to do anything here right now; the app ID and password may be left undefined at this time.

The EchoBot.csproj file specifies dependencies and their associated versions for your bot. This is all set up by the template and your system. Additional dependencies can be installed using NuGet package manager or the dotnet add package command.

Resource provisioning

The bot as a web app needs to create a web service, bot adapter, and bot object.

Many bots would also create the storage layer and memory management objects for the bot, but the echo bot does not require state. Other bots would also create any objects external to the bot object or adapter that either need to consume.

In ASP.NET, you register objects and object creation methods in the Startup.cs file. The ConfigureServices method loads the connected services, as well as their keys from appsettings.json or Azure Key Vault (if there are any), connects state, and so on. Here, the adapter and bot are defined to be available through dependency injection. Then, the Configure method finishes the configuration of your app.

ConfigureServices and Configure are called by the runtime when the app starts.

Messaging endpoint

The template implements a web service with a messaging endpoint. The service extracts the authentication header and request payload and forwards them to the adapter.

The C# and JavaScript SDKs support streaming connections. While the echo bot does not use any of the streaming features, the adapter in the template is designed to support them.

Each incoming request represents the start of a new turn.

Controllers\BotController.cs

// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
// achieved by specifying a more specific type for the bot constructor argument.
[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
    private readonly IBotFrameworkHttpAdapter Adapter;
    private readonly IBot Bot;

    public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
    {
        Adapter = adapter;
        Bot = bot;
    }

    [HttpPost, HttpGet]
    public async Task PostAsync()
    {
        // Delegate the processing of the HTTP POST to the adapter.
        // The adapter will invoke the bot.
        await Adapter.ProcessAsync(Request, Response, Bot);
    }
}

The bot adapter

The adapter receives activities from the messaging endpoint, forwards them to the bot's turn handler, and catches any errors or exceptions the bot's logic doesn't catch.

The adapter allows you to add your own on turn error handler.

Startup.cs

The adapter to use is defined in the ConfigureServices method.

// Create the Bot Framework Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();

AdapterWithErrorHandler.cs

public class AdapterWithErrorHandler : CloudAdapter
{
    public AdapterWithErrorHandler(IConfiguration configuration, IHttpClientFactory httpClientFactory, ILogger<IBotFrameworkHttpAdapter> logger)
        : base(configuration, httpClientFactory, logger)
    {
        OnTurnError = async (turnContext, exception) =>
        {
            // Log any leaked exception from the application.
            // NOTE: In production environment, you should consider logging this to
            // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how
            // to add telemetry capture to your bot.
            logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");

            // Send a message to the user
            await turnContext.SendActivityAsync("The bot encountered an error or bug.");
            await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code.");

            // Send a trace activity, which will be displayed in the Bot Framework Emulator
            await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
        };
    }
}

The bot logic

The echo bot uses an activity handler and implements handlers for the activity types it will recognize and react to, in this case, the conversation update and message activities.

  • A conversation update activity includes information on who has joined or left the conversation. For non-group conversations, both the bot and the user join the conversation when it starts. For group conversations, a conversation update is generated whenever someone joins or leaves the conversation, whether that's the bot or a user.
  • A message activity represents a message the user sends to the bot.

The echo bot welcomes a user when they join the conversation and echoes back any messages they send to the bot.

Startup.cs

The bot to use is defined in the ConfigureServices method.

// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, EchoBot>();

Bots\EchoBot.cs

public class EchoBot : ActivityHandler
{
    protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
    {
        var replyText = $"Echo: {turnContext.Activity.Text}";
        await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
    }

    protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
    {
        var welcomeText = "Hello and welcome!";
        foreach (var member in membersAdded)
        {
            if (member.Id != turnContext.Activity.Recipient.Id)
            {
                await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
            }
        }
    }
}

Next steps