The Bot Framework templates and samples are written for ASP.NET (C#), restify (JavaScript), and aiohttp (Python).
However, the web service features aren't 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 Create a bot, 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. However, this article doesn't cover streaming features.
Note
The Bot Framework JavaScript, C#, and Python SDKs will continue to be supported, however, the Java SDK is being retired with final long-term support ending in November 2023.
Existing bots built with the Java SDK will continue to function.
The Bot Framework includes both VSIX and .NET 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 aren't bot-specific.
Note
You can install the templates from within Visual Studio.
In the menu, select Extensions then Manage Extensions.
In the Manage Extensions dialog, search for and install Bot Framework v4 SDK templates for Visual Studio.
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'll 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.
The Yeoman generator creates a type of restify web application. If you look at the restify quickstart in their docs, you'll see an app similar to the generated index.js file. Some of the key files generated by the template are described in this article. Code in some files won't be copied, but you'll see it when you run the bot, and you can refer to the Node.js echobot sample.
The package.json 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 npm install.
The .env file specifies the configuration information for your bot, such as the port number, app ID, and password among other things. If using certain technologies or using this bot in production, you'll 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.
To use the .env configuration file, the bot requires the dotenv package from npm. This is already included as a dependency in the package.json file.
The Yeoman generator creates a Spring based web application with a build file using Maven.
The Maven pom.xml 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 by adding entries to the pom.xml file.
The application.properties file specifies the configuration information for your bot, such as the port number, app ID, and password among other things. If using certain technologies or using this bot in production, you'll 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 Python Cookiecutter templates create an aiohttp web application.
The requirements.txt file specifies dependencies and their associated versions for your bot. This is all setup by the template and your system. Additional dependencies can be installed using pip install -r requirements.txt
The config.py file specifies the configuration information for your bot, such as the port number, app ID, and password among other things. If using certain technologies or using this bot in production, you'll 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.
Resource provisioning
To function as a web app, your bot needs to create a web service, bot adapter, and bot object.
For most bots, you would also create storage layer and memory management objects for the bot.
However, the echo bot doesn't need to persist state between turns.
And for some bots, you may need to create other objects that the bot object or adapter will require.
In ASP.NET, you register objects and object creation methods in the Startup.cs file.
The ConfigureServices method loads the connected services and their keys (if there are any) from appsettings.json, 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.
In restify, you set up the web service and the objects it needs in the index.js file. The service, adapter, and bot are covered separately in the following sections.
In Spring, you set up the web service and the objects it needs in the application.java file. The application.java has comments that denote the different components and framework classes used by the bot application. The service, adapter, and bot are covered separately in the following sections.
In aiohttp, you set up the web service and the objects it needs in the app.py file. The service, adapter, and bot are covered separately in the following sections.
Messaging endpoint
The template implements a web service with a messaging endpoint.
When it receives a request, 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 doesn't use any of the streaming features, the adapter in the C# and JavaScript templates is designed to support them.
Each incoming request represents the start of a new turn.
// 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);
}
}
index.js
const { EchoBot } = require('./bot');
// Create HTTP server
const server = restify.createServer();
server.use(restify.plugins.bodyParser());
// Create an adapter scoped to this WebSocket connection to allow storing session data.
const streamingAdapter = new CloudAdapter(botFrameworkAuthentication);
// Set onTurnError for the CloudAdapter created for each connection.
streamingAdapter.onTurnError = onTurnErrorHandler;
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 also forwards activities from your bot to the user's channel.
The adapter allows you to add your own on turn error handler.
The adapter to use is defined in the ConfigureServices method.
// Create the Bot Framework Authentication to be used with the Bot Adapter.
services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
AdapterWithErrorHandler.cs
public class AdapterWithErrorHandler : CloudAdapter
{
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger<IBotFrameworkHttpAdapter> logger)
: base(auth, 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");
};
}
}
index.js
// application insights. See https://aka.ms/bottelemetry for telemetry
// configuration instructions.
console.error(`\n [onTurnError] unhandled error: ${ error }`);
// Send a trace activity, which will be displayed in Bot Framework Emulator
await context.sendTraceActivity(
'OnTurnError Trace',
`${ error }`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
await context.sendActivity('The bot encountered an error or bug.');
await context.sendActivity('To continue to run this bot, please fix the bot source code.');
};
// Set the onTurnError for the singleton CloudAdapter.
adapter.onTurnError = onTurnErrorHandler;
// Create the main dialog.
const myBot = new EchoBot();
// Listen for incoming requests.
server.post('/api/messages', async (req, res) => {
// Route received a request to adapter for processing
await adapter.process(req, res, (context) => myBot.run(context));
Application.java
The adapter to use is defined in the getBotFrameworkAdapter method.
/**
* Returns a custom Adapter that provides error handling.
*
* @param configuration The Configuration object to use.
* @return An error handling BotFrameworkHttpAdapter.
*/
@Override
public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) {
return new AdapterWithErrorHandler(configuration);
}
The AdapterWithErrorHandler is defined in the Java SDK code, in the com.microsoft.bot.integration package. This class can be reviewed in the source code for the Java SDK.
app.py
from botbuilder.core import (
TurnContext,
)
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication
# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG))
# Catch-all for errors.
# This check writes out errors to console log .vs. app insights.
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
traceback.print_exc()
# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")
await context.send_activity(
"To continue to run this bot, please fix the bot source code."
)
# Send a trace activity if we're talking to the Bot Framework Emulator
if context.activity.channel_id == "emulator":
# Create a trace activity that contains the error object
trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error}",
value_type="https://www.botframework.com/schemas/error",
)
# Send a trace activity, which will be displayed in Bot Framework Emulator
await context.send_activity(trace_activity)
ADAPTER.on_turn_error = on_error
# Create the Bot
BOT = EchoBot()
The bot logic
The echo bot uses an activity handler and implements handlers for the activity types it recognizes and reacts 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.
The bot to use is defined in the ConfigureServices method.
// Create the Bot Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
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);
}
}
}
}
index.js
const { EchoBot } = require('./bot');
});
// Listen for Upgrade requests for Streaming.
bot.js
const { ActivityHandler, MessageFactory } = require('botbuilder');
class EchoBot extends ActivityHandler {
constructor() {
super();
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
this.onMessage(async (context, next) => {
const replyText = `Echo: ${ context.activity.text }`;
await context.sendActivity(MessageFactory.text(replyText, replyText));
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
const welcomeText = 'Hello and welcome!';
for (let cnt = 0; cnt < membersAdded.length; ++cnt) {
if (membersAdded[cnt].id !== context.activity.recipient.id) {
await context.sendActivity(MessageFactory.text(welcomeText, welcomeText));
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
Application.java
The bot to use is defined in the getBot method.
/**
* Returns the Bot for this application.
*
* <p>
* The @Component annotation could be used on the Bot class instead of this method
* with the @Bean annotation.
* </p>
*
* @return The Bot implementation for this application.
*/
@Bean
public Bot getBot() {
return new EchoBot();
}
from botbuilder.core import (
TurnContext,
)
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication
bots/bot.py
from botbuilder.core import ActivityHandler, MessageFactory, TurnContext
from botbuilder.schema import ChannelAccount
class EchoBot(ActivityHandler):
async def on_members_added_activity(
self, members_added: [ChannelAccount], turn_context: TurnContext
):
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity("Hello and welcome!")
async def on_message_activity(self, turn_context: TurnContext):
return await turn_context.send_activity(
MessageFactory.text(f"Echo: {turn_context.activity.text}")
)