.NET (C#) โ
๐ฆ Botas on NuGet ยท ๐ API Reference
Project setup โ
The botas library targets .NET 10. Add a NuGet package reference to your ASP.NET Core web project:
<ItemGroup>
<!-- allow pre-release versions -->
<PackageReference Include="Botas" Version="0.1.*-*" />
</ItemGroup>The library brings in Microsoft.Identity.Web, Microsoft.AspNetCore.Authentication.JwtBearer, and related identity packages automatically โ you don't need to add them yourself.
Quick start with BotApp โ
The simplest way to create a bot in .NET is to use BotApp.Create(). It configures ASP.NET Core, JWT authentication, and the /api/messages endpoint in a single call:
using Botas;
var app = BotApp.Create(args);
app.On("message", async (ctx, ct) =>
{
await ctx.SendAsync($"You said: {ctx.Activity.Text}", ct);
});
app.Run();That's it โ a fully working bot in just a few lines.
What BotApp.Create() does โ
Under the hood, BotApp.Create(args) calls:
WebApplication.CreateSlimBuilder(args)โ minimal ASP.NET Core host (no Razor, MVC, etc.)- AddBotApplication<
BotApplication>() โ registers the bot as a singleton, sets up JWT authentication, authorization policies, and authenticated HTTP clients Build()โ builds theWebApplication- UseBotApplication<
BotApplication>() โ mapsPOST /api/messages, enables auth middleware, and returns the bot instance
The returned object is a wrapper that exposes both the BotApplication (for handler registration) and the WebApplication (for running the host).
Handler registration with app.On() โ
Use app.On(type, handler) to register per-activity-type handlers. The handler receives a TurnContext (not a raw CoreActivity):
app.On("message", async (ctx, ct) =>
{
// ctx.Activity is the incoming activity
// ctx.SendAsync() sends a reply
await ctx.SendAsync($"Echo: {ctx.Activity.Text}", ct);
});If no handler is registered for an incoming activity type, the activity is silently ignored โ no error is thrown.
Sending replies with ctx.SendAsync() โ
TurnContext.SendAsync() is the simplest way to send a reply:
// Send text
await ctx.SendAsync("Hello!", ct);
// Send a full activity
await ctx.SendAsync(new CoreActivity
{
Type = "message",
Text = "Hello!",
Conversation = ctx.Activity.Conversation,
ServiceUrl = ctx.Activity.ServiceUrl
}, ct);SendAsync(string) automatically creates a properly-addressed reply with the given text. SendAsync(CoreActivity) sends the activity as-is through the authenticated ConversationClient.
Advanced: Manual ASP.NET Core integration โ
For advanced scenarios โ custom DI lifetimes, multi-bot hosting, or manual middleware configuration โ you can skip BotApp.Create() and wire things up manually.
AddBotApplication<T> โ register services โ
Call this on IServiceCollection during host construction. It registers your bot as a singleton, sets up JWT authentication, authorization policies, and the authenticated HTTP clients used to call the Bot Service REST API.
WebApplicationBuilder webAppBuilder = WebApplication.CreateSlimBuilder(args);
webAppBuilder.Services.AddBotApplication<BotApplication>();Under the hood, AddBotApplication<T> calls:
AddBotAuthorizationโ configures two JWT bearer schemes (BotandAgent) that validate tokens from the Bot Service and your Azure AD tenant, and wires them into aDefaultPolicyauthorization policy.AddBotApplicationClientsโ registersConversationClientwith pre-configuredHttpClientinstances that automatically attach an outbound OAuth2 bearer token (scopehttps://api.botframework.com/.default).
UseBotApplication<T> โ map the endpoint โ
Call this on the built WebApplication. It enables authentication/authorization middleware and maps POST /api/messages to your bot's ProcessAsync pipeline, protected by the DefaultPolicy.
WebApplication webApp = webAppBuilder.Build();
var botApp = webApp.UseBotApplication<BotApplication>();The route path and authorization policy name can be customized:
var botApp = webApp.UseBotApplication<BotApplication>(
routePath: "bot/incoming",
authorizationPolicy: "MyCustomPolicy");UseBotApplication returns the resolved BotApplication instance so you can configure handlers and middleware on it before calling webApp.Run().
Middleware โ
Middleware lets you inspect or transform every incoming activity before the handler runs. Implement ITurnMiddleWare and call next to continue the pipeline, or skip next to short-circuit:
public class LoggingMiddleware : ITurnMiddleWare
{
public async Task OnTurnAsync(
TurnContext context,
NextDelegate next,
CancellationToken cancellationToken = default)
{
Console.WriteLine($">> Incoming: {context.Activity.Type}");
await next(cancellationToken); // continue to next middleware / handler
Console.WriteLine($"<< Done: {context.Activity.Type}");
}
}Register middleware with Use(). Middleware executes in registration order:
app.Use(new LoggingMiddleware());
app.Use(new MetricsMiddleware());The pipeline flows like this:
LoggingMiddleware.OnTurnAsync โ
MetricsMiddleware.OnTurnAsync โ
Handler (from app.On())For more examples, see the Middleware guide.
Error handling โ
If an activity handler throws an exception, it is wrapped in a BotHandlerException that carries the original exception and the triggering CoreActivity:
using Botas;
try
{
// processAsync is called internally by the framework
}
catch (BotHandlerException ex)
{
Console.WriteLine($"Handler for '{ex.Activity.Type}' failed: {ex.InnerException?.Message}");
}BotHandlerException carries:
InnerExceptionโ the original exceptionActivityโ theCoreActivitythat triggered the error
If no app.On() handlers are registered, the bot silently acknowledges every request with an empty 200 OK response.
CoreActivity schema โ
CoreActivity uses System.Text.Json with [JsonExtensionData] to preserve unknown JSON properties:
| Property | Type | Description |
|---|---|---|
Type | string | Activity type ("message", "typing", etc.) |
ServiceUrl | string | The channel's service endpoint |
From | ChannelAccount? | Sender |
Recipient | ChannelAccount? | Recipient |
Conversation | Conversation? | Conversation reference |
Text | string? | Message text |
Entities | JsonArray? | Attached entities |
Attachments | List<Attachment>? | Attached files/cards |
Any additional JSON properties are preserved in the [JsonExtensionData] dictionary and round-trip safely through serialization.
ConversationClient โ
For advanced scenarios, ConversationClient exposes the Conversations REST API:
| Method | Description |
|---|---|
SendActivityAsync | Send an activity to a conversation |
The TurnContext.SendAsync() method is the recommended way to send replies โ it uses ConversationClient under the hood with proper addressing.
Teams features โ
Use TeamsActivityBuilder to send mentions, adaptive cards, and suggested actions. See the Teams Features guide for full examples.
API Reference โ
Full API documentation is generated with DocFX from XML doc comments:
๐ .NET API Reference
// Echo with a mention
var sender = ctx.Activity.From!;
var reply = new TeamsActivityBuilder()
.WithConversationReference(ctx.Activity)
.WithText($"<at>{sender.Name}</at> said: {ctx.Activity.Text}")
.AddMention(sender)
.Build();
await ctx.SendAsync(reply, ct);Use TeamsActivity.FromActivity() to access Teams-specific metadata:
var teamsActivity = TeamsActivity.FromActivity(ctx.Activity);
var tenantId = teamsActivity.ChannelData?.Tenant?.Id;Configuration โ
The bot reads Azure AD credentials from the AzureAd configuration section. In development you typically use user secrets, environment variables, or the .env bridge script; in production use Azure App Configuration or Key Vault.
Option 1 โ Shared .env file (recommended for development) โ
If you keep a .env file at the repository root (shared with Node.js and Python samples), use the helper script to generate a launchSettings.json:
node dotnet/env-to-launch-settings.mjs EchoBotThis reads CLIENT_ID, CLIENT_SECRET, and TENANT_ID from .env and maps them to the ASP.NET Core equivalents (AzureAd:ClientId, AzureAd:ClientCredentials:0:*, etc.). The generated file lives at samples/EchoBot/Properties/launchSettings.json and is already gitignored.
To generate for all samples at once, omit the sample name:
node dotnet/env-to-launch-settings.mjsOption 2 โ appsettings.json โ
Minimal appsettings.json (add the $schema for IntelliSense in VS / VS Code):
{
"$schema": "https://json.schemastore.org/appsettings.json",
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "<your-bot-app-id>",
"TenantId": "<your-tenant-id>",
"ClientCredentials": [
{
"SourceType": "ClientSecret",
"ClientSecret": "<your-client-secret>"
}
]
}
}Note: Microsoft.Identity.Web v3+ requires the
ClientCredentialsarray format. The flat"ClientSecret"property is no longer supported.
Option 3 โ Environment variables โ
Use the ASP.NET Core double-underscore convention:
export AzureAd__ClientId="<your-bot-app-id>"
export AzureAd__TenantId="<your-tenant-id>"
export AzureAd__ClientCredentials__0__SourceType="ClientSecret"
export AzureAd__ClientCredentials__0__ClientSecret="<your-client-secret>"For setup details on Azure Bot registration and credentials, see the Setup Guide and Authentication.
Key types reference โ
| Type | Description |
|---|---|
BotApplication | Main bot class โ owns the handler, middleware pipeline, and send methods |
CoreActivity | Deserialized Bot Service activity; preserves unknown JSON properties via [JsonExtensionData] |
ChannelAccount | Represents a user or bot identity (Id, Name, AadObjectId, Role) |
Conversation | Conversation identifier (Id), also preserves extension data |
ConversationClient | Sends outbound activities over the authenticated HTTP client |
ITurnMiddleWare | Middleware interface โ implement OnTurnAsync |
BotHandlerException | Wraps handler exceptions with the triggering activity |
TeamsActivity | Teams-specific activity โ ChannelData, Timestamp, Locale, SuggestedActions, and FromActivity() factory |
TeamsActivityBuilder | Fluent builder for Teams replies โ AddMention(), AddAdaptiveCardAttachment(), WithSuggestedActions() |
TeamsChannelData | Typed Teams channel metadata โ Tenant, Channel, Team, Meeting, Notification |
SuggestedActions | Quick-reply buttons โ contains CardAction[] |
Entity | Activity entity (e.g. mention) with extension data |
Attachment | File or card attachment with ContentType, Content, and extension data |