Application Model¶
TeleFlow applications use the same shape as normal .NET services:
- create an application builder;
- register services;
- register Telegram framework services;
- register handlers;
- register one update source;
- build and run.
var builder = TeleFlowApplication.CreateBuilder(args);
builder.Services.AddTelegramBot(options => options.Token = token);
builder.Services.AddMemoryStateStorage();
builder.Services.AddTelegramHandlersFromAssembly(typeof(Program).Assembly);
builder.Services.AddLongPolling();
await using var app = builder.Build();
await app.RunAsync();
Core Concepts¶
Update Source¶
An update source produces updates. Framework long polling and framework webhooks are update sources. Only one IUpdateSource should be registered for a running application.
Update Processor¶
The update processor creates an UpdateContext, runs middleware, and dispatches the update.
Middleware¶
Middleware wraps update execution. Built-in middleware covers logging, exception handling, state, and rate limiting where registered.
Dispatcher¶
The dispatcher selects and invokes the matching handler. Telegram-specific dispatching lives outside TeleFlow.Core.
Handler¶
A handler is a normal class with a method that TeleFlow can invoke. Routing metadata comes from attributes.
Telegram Contexts¶
Telegram handlers receive context objects that expose the current update, the Telegram client, state, and small action helpers.
Common properties from TelegramUpdateContext:
| Property | Meaning |
|---|---|
Bot |
The low-level ITelegramClient. Use it for full Bot API access. |
Update |
The raw Telegram Update. |
State |
Current update state facade. |
Wizard |
Wizard navigation facade over state history. |
Chat |
Chat action helper for typing, upload actions, and similar indicators. |
CancellationToken |
The framework cancellation token for the current update. |
Services |
Service provider for advanced scenarios. Prefer DI parameters or constructors in normal handler code. |
Message handlers use MessageContext:
[Command("whoami")]
public Task WhoAmI(MessageContext ctx, CancellationToken ct)
{
var id = ctx.Sender?.Id;
var name = ctx.User?.FullName ?? "unknown";
return ctx.Message.AnswerAsync($"User: {name}, id: {id}", ct);
}
ctx.Message contains message actions such as AnswerAsync, ReplyAsync, AnswerPhotoAsync, ReplyDocumentAsync, and DeleteAsync. These helpers target the current chat. Use ctx.Bot.*Async when the target chat or method surface should be explicit.
Callback handlers use CallbackQueryContext:
[Callback]
public async Task Handle(CallbackQueryContext ctx, CancellationToken ct)
{
await ctx.Callback.AnswerAsync(ct);
await ctx.Callback.EditTextAsync("Done.", ct);
}
Chat member handlers use ChatMemberUpdatedContext:
[ChatMemberUpdated]
public Task Audit(ChatMemberUpdatedContext ctx, IAuditLog audit, CancellationToken ct)
{
return audit.RecordAsync(ctx.TelegramChat.Id, ctx.Member.Id, ct);
}
ctx.Chat.ActionAsync(...) sends a chat action immediately and keeps it alive until the returned lease is disposed:
await using var typing = await ctx.Chat.ActionAsync(ChatAction.Typing, ct);
await reportService.BuildAsync(ct);
await ctx.Message.AnswerAsync("Report is ready.", ct);
Why TeleFlow.Core Is Transport-Agnostic¶
TeleFlow.Core owns application, middleware, update processing, state contracts, and replacement points. It does not know about Telegram message fields, callbacks, Bot API methods, or Telegram update types.
Telegram behavior lives in Telegram packages:
TeleFlow.Telegram.ClientTeleFlow.Telegram.FrameworkTeleFlow.Telegram.Framework.LongPollingTeleFlow.Telegram.Framework.Webhooks- raw transport packages
This keeps the dependency direction clean and makes the framework easier to test.
Startup Failure Is Intentional¶
TeleFlow prefers early configuration errors over best-effort ambiguity. Examples:
AddTelegramBot(...)must be called before framework handlers and transports.AddLongPolling(...)andAddWebhook(...)cannot both ownIUpdateSource.AddTelegramHandlersFromAssembly(...)requires generated metadata.- Options are validated when they are registered.
This is deliberate. A bot should fail during startup when configuration is wrong, not after the first production update arrives.
Minimal Direct Client App¶
If you do not need handlers, use the client package directly:
using Microsoft.Extensions.DependencyInjection;
using TeleFlow.Telegram;
var services = new ServiceCollection();
services.AddTelegramClient(options =>
{
options.Token = token;
});
using var provider = services.BuildServiceProvider();
var bot = provider.GetRequiredService<ITelegramClient>();
var me = await bot.GetMeAsync();
Console.WriteLine(me.Username);
Minimal Framework App¶
Use the framework when you want routing, filters, callbacks, state, and transports:
builder.Services.AddTelegramBot(options => options.Token = token);
builder.Services.AddTelegramHandlersFromAssembly(typeof(Program).Assembly);
builder.Services.AddLongPolling();
Replacing Core Policies¶
TeleFlow exposes replacement APIs for real extension points:
services.AddUpdateSource<MyUpdateSource>();
services.AddUpdateDispatcher<MyDispatcher>();
services.AddCallbackDataSerializer<MySerializer>();
services.AddStateStore<MyStateStore>();
services.AddStateDataStore<MyStateDataStore>();
services.AddStateHistoryStore<MyHistoryStore>();
services.AddStateKeyFactory<MyStateKeyFactory>();
These are advanced APIs. Most applications should start with framework transports and memory storage, then replace only the part that has become an infrastructure requirement.