88 KiB
Getting Started with Telegrator
This guide will walk you through the core concepts and advanced features of Telegrator — a modern, aspect-oriented, mediator-based framework for building powerful and maintainable Telegram bots in C#.
- 1. Installation
- 2. Quick Start
- 3. Core Concepts
- 4. Intermediate Topics
- 5. Advanced Topics
- 6. Integration & Deployment
- 7. Best Practices & Patterns
- 8. FAQ & Troubleshooting
- 9. Links
1. Installation
Telegrator is distributed as a NuGet package. You can install it using the .NET CLI, the NuGet Package Manager Console, or by managing NuGet packages in Visual Studio.
Prerequisites
- .NET >= 5.0
or.NET Core >= 2.0orFramework >= 4.6.1 (.NET Standard 2.1 compatible) - A Telegram Bot Token from @BotFather.
.NET CLI
dotnet add package Telegrator
Package Manager Console
Install-Package Telegrator
Hosting Integrations
- .NET Core >= 10.0
Telegrator.Hosting: For console/background servicesTelegrator.Hosting.Web: For ASP.NET Core/Webhook
# For console/background services
dotnet add package Telegrator.Hosting
# For webhook hosting
dotnet add package Telegrator.Hosting.Web
2. Quick Start
This section will get you up and running with Telegrator quickly. You'll learn the basics and create your first bot in minutes.
2.1. Your First Bot
Let's create a simple bot that replies to any private message containing "hello":
using Telegrator;
using Telegrator.Handlers;
using Telegrator.Annotations;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
[MessageHandler]
[ChatType(ChatType.Private)]
[TextContains("hello", StringComparison.InvariantCultureIgnoreCase)]
public class HelloHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Hello! Nice to meet you!", cancellationToken: cancellation);
return Ok;
}
}
class Program
{
static void Main(string[] args)
{
var bot = new TelegratorClient("<YOUR_BOT_TOKEN>");
bot.Handlers.AddHandler<HelloHandler>();
bot.StartReceiving();
Console.ReadLine();
}
}
How is it working?
[MessageHandler]: Marks the class as a handler for message updates[ChatType(ChatType.Private)]: Only processes private chat messages[TextContains("hello")]: Only processes messages containing "hello" (case-insensitive)TelegratorClient: Main bot client that manages Telegram connectionbot.Handlers.AddHandler<HelloHandler>(): Registers the handlerbot.StartReceiving(): Starts the long-polling loopReply(...): Sends a reply to the original message
2.2. Basic Handler Types
Telegrator provides several handler types for different update types:
MessageHandler
Handles text messages and media:
[MessageHandler]
[TextContains("hello")]
public class GreetingHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Hello there!");
return Ok;
}
}
CommandHandler
Handles bot commands (messages starting with /):
[CommandHandler]
[CommandAlias("start")]
public class StartHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Welcome! Use /help to see available commands.");
return Ok;
}
}
CallbackQueryHandler
Handles button clicks and inline keyboard interactions:
[CallbackQueryHandler]
[TextStartsWith("action_")]
public class ActionHandler : CallbackQueryHandler
{
public override async Task<Result> Execute(IHandlerContainer<CallbackQuery> container, CancellationToken cancellation)
{
await AnswerCallbackQuery("Action completed!");
return Ok;
}
}
AnyUpdateHandler
Handles any type of update:
[AnyUpdateHandler]
public class LoggingHandler : AnyUpdateHandler
{
public override async Task<Result> Execute(IHandlerContainer<Update> container, CancellationToken cancellation)
{
Console.WriteLine($"Received update: {container.HandlingUpdate.Type}");
return Ok;
}
}
2.3. Simple Filters
Filters determine when your handlers should run. Here are the most common ones:
Text Filters
[TextContains("hello")] // Message contains "hello"
[TextStartsWith("/")] // Message starts with "/"
[TextStartsWith("/", Modifiers = FilterModifier.Not)] // Message does NOT start with "/"
User Filters
[FromUserId(123456789)] // Only from specific user ID
[FromUser("John")] // Only from user with specific name
[FromUsername("john_doe")] // Only from user with specific username
Chat Filters
[ChatType(ChatType.Private)] // Only private chats
[ChatType(ChatType.Group)] // Only group chats
[Mentioned] // Only if bot was mentioned with @
Command Filters
[CommandAlias("start")] // Only for /start command
[CommandAlias("help")] // Only for /help command
Combining Filters
Filters are combined with AND logic by default. You can use modifiers:
[MessageHandler]
[ChatType(ChatType.Private)]
[TextContains("hello", Modifiers = FilterModifier.Not)]
public class NotHelloHandler : MessageHandler
{
// Runs for private messages that do NOT contain "hello"
}
[MessageHandler]
[TextContains("bot", Modifiers = FilterModifier.OrNext)]
[Mentioned()]
public class BotMentionHandler : MessageHandler
{
// Runs for messages that contain "bot" OR if bot was mentioned
}
Filter Modifiers:
FilterModifier.Not- Inverts the filterFilterModifier.OrNext- Combines with next filter using OR logic- Can be combined:
Modifiers = FilterModifier.Not | FilterModifier.OrNext
3. Core Concepts
This section covers the fundamental concepts and architecture of Telegrator.
3.1. Handler System
Telegrator is built around several core ideas:
- Aspect-Oriented Handlers: Each handler is a focused, reusable class that reacts to a specific type of update (message, command, callback, etc.).
- Aspect-Oriented Programming: Built-in support for pre and post-execution processing through aspects, enabling separation of cross-cutting concerns.
- Mediator Pattern: All updates are routed through a central
UpdateRouter, which dispatches them to the appropriate handlers based on filters and priorities. - Filters as Attributes: Handler classes are decorated with filter attributes that declaratively specify when the handler should run.
- State Management: Built-in mechanisms for managing user/chat state without external storage.
- Concurrency Control: Fine-grained control over how many handlers run in parallel, both globally and per-handler.
Handler Lifecycle
- Registration: Handlers are registered with the bot during startup
- Discovery: The framework automatically discovers handlers using reflection
- Filtering: Updates are filtered to determine which handlers should run
- Execution: Selected handlers are executed in order of priority
- Cleanup: Resources are cleaned up after execution
Handler Priority & Importance
- Importance: Internal priority based on handler type (CommandHandler > MessageHandler > AnyUpdateHandler)
- Priority: User-defined global priority for execution order
- Combined Order: Importance first, then Priority
Implicit Handlers from Methods
You can create handlers directly from methods without defining full handler classes. This is useful for simple handlers or quick prototyping:
// Simple echo handler
[MessageHandler]
[TextStartsWith("/", Modifiers = FilterModifier.Not)]
private static async Task<Result> EchoHandler(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
await container.Reply($"You said: \"{container.Input.Text}\"", cancellationToken: cancellationToken);
return Ok;
}
// Command handler with inline keyboard
[CommandHandler]
[CommandAlias("menu")]
private static async Task<Result> MenuHandler(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
var keyboard = new InlineKeyboardMarkup(new[]
{
InlineKeyboardButton.WithCallbackData("Option 1", "option1"),
InlineKeyboardButton.WithCallbackData("Option 2", "option2")
});
await container.Reply("Choose an option:", replyMarkup: keyboard, cancellationToken: cancellationToken);
return Ok;
}
// Callback query handler
[CallbackQueryHandler]
[CallbackData("option1")]
private static async Task<Result> Option1Handler(IHandlerContainer<CallbackQuery> container, CancellationToken cancellationToken)
{
await container.AnswerCallbackQuery("You selected Option 1!", cancellationToken: cancellationToken);
await container.EditMessage("You selected Option 1!");
return Ok;
}
// Register all methods as handlers
builder.Handlers.AddMethod<Message>(EchoHandler);
builder.Handlers.AddMethod<Message>(MenuHandler);
builder.Handlers.AddMethod<CallbackQuery>(Option1Handler);
Advanced Example with State Management:
public enum UserState
{
Start,
WaitingForName,
WaitingForAge
}
// Start conversation
[CommandHandler]
[CommandAlias("register")]
[State<UserState>(UserState.Start)]
private static async Task<Result> StartRegistration(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply("Please enter your name:", cancellationToken: cancellationToken);
return Ok;
}
// Handle name input
[MessageHandler]
[State<UserState>(UserState.WaitingForName)]
private static async Task<Result> HandleName(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
var name = container.Input.Text;
StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply($"Hello {name}! Please enter your age:", cancellationToken: cancellationToken);
return Ok;
}
// Handle age input
[MessageHandler]
[State<UserState>(UserState.WaitingForAge)]
private static async Task<Result> HandleAge(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
if (int.TryParse(container.Input.Text, out int age))
{
StateStorage.GetStateMachine<UserState>().BySenderId().Reset();
await container.Reply($"Registration complete! Name: {name}, Age: {age}", cancellationToken: cancellationToken);
}
else
{
await container.Reply("Please enter a valid age (number):", cancellationToken: cancellationToken);
}
return Ok;
}
// Register state management handlers
builder.Handlers.AddMethod<Message>(StartRegistration);
builder.Handlers.AddMethod<Message>(HandleName);
builder.Handlers.AddMethod<Message>(HandleAge);
How is it working?
- Method Signature: Methods must return
Task<Result>and acceptIHandlerContainer<T>andCancellationToken- Attributes: Apply the same filter attributes as regular handlers (
[MessageHandler],[CommandHandler], etc.)- Container Methods: Use extension methods like
container.Reply(),container.Response(), etc.- Registration: Use
AddMethod<T>()to register methods as handlers- State Management: Same state management patterns as regular handlers
- Flexibility: Can be used for simple handlers or complex multi-step conversations
3.2. Filter System
Filters are the gatekeepers of your bot logic. They determine when handlers should be executed.
Filter Types
- Text Filters:
TextContains,TextStartsWith,RegexFilter - User Filters:
FromUserId,FromUser,FromUsername - Chat Filters:
ChatType,Mentioned,HasReply - Command Filters:
CommandAlias - State Filters:
EnumState,NumericState,StringState
Filter Composition
- AND Logic: Multiple filters are combined with AND by default
- OR Logic: Use
FilterModifier.OrNextto combine with OR - NOT Logic: Use
FilterModifier.Notto invert a filter - Combined Modifiers: Use bitwise OR to combine modifiers
Custom Filters
You can create custom filters by inheriting from FilterAnnotation<T>:
public class AdminOnlyAttribute : FilterAnnotation<Message>
{
private readonly List<long> _adminIds = [];
public void AddAdmin(long id)
=> _adminIds.Add(id);
public override bool CanPass(FilterExecutionContext<Message> context)
=> _adminIds.Contains(context.Input.From?.Id);
}
Hosting Integration - Access to DI Container
When using Telegrator.Hosting, filters can access the DI container and configuration through HostedTelegramBotInfo:
public class DatabaseUserFilterAttribute : FilterAnnotation<Message>
{
public override bool CanPass(FilterExecutionContext<Message> context)
{
if (context.BotInfo is not HostedTelegramBotInfo botInfo)
return false;
using (var scope = botInfo.Services.CreateScope())
{
var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
var dbContext = scope.ServiceProvider.GetRequiredService<UsersDbContext>();
var telegramId = context.Input.From?.Id;
if (telegramId == null)
return false;
var user = dbContext.Users.FirstOrDefault(u => u.TelegramId == telegramId);
return user?.IsActive == true;
}
}
}
// Usage in handler
[MessageHandler]
[DatabaseUserFilter]
public class ActiveUserHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Hello, active user!");
return Ok;
}
}
Configuration-based Filter:
public class ConfigurableFilterAttribute : FilterAnnotation<Message>
{
private readonly string _configKey;
public ConfigurableFilterAttribute(string configKey)
{
_configKey = configKey;
}
public override bool CanPass(FilterExecutionContext<Message> context)
{
if (context.BotInfo is not HostedTelegramBotInfo botInfo)
return false;
var configuration = botInfo.Services.GetRequiredService<IConfiguration>();
var allowedUsers = configuration.GetSection(_configKey).Get<List<long>>() ?? [];
return allowedUsers.Contains(context.Input.From?.Id ?? 0);
}
}
// Usage
[MessageHandler]
[ConfigurableFilter("AllowedUsers")]
public class RestrictedHandler : MessageHandler
{
// Handler implementation
}
Key Points:
- Hosting Only: This feature is only available when using
Telegrator.Hosting - Type Casting: Cast
context.BotInfotoHostedTelegramBotInfo - Service Access: Use
botInfo.Services.GetRequiredService<T>()to access DI services - Configuration Access: Use
botInfo.Services.GetRequiredService<IConfiguration>()for settings - Null Safety: Always check if the cast is successful before using services
3.3. State Management
Telegrator provides built-in state management for multi-step conversations (wizards, forms, quizzes) with or without a database.
Note
Each type of
StateKeeper's keys and states are shared between EVERY handler in project.
How to Use:
- Define your state (enum/int/string)
- Use a state filter attribute on your handler:
[State<MyEnum>(MyEnum.Step1)]
- Change state inside the handler using extension methods:
StateStorage.GetStateMachine<MyEnum>().BySenderId().Current()StateStorage.GetStateMachine<MyEnum>().BySenderId().Advance()StateStorage.GetStateMachine<MyEnum>().BySenderId().Retreat()StateStorage.GetStateMachine<MyEnum>().BySenderId().Reset()
Example:
public enum QuizState
{
Start, Q1, Q2
}
[CommandHandler]
[CommandAlias("quiz")]
[State<QuizState>(QuizState.Start)]
public class StartQuizHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
StateStorage.GetStateMachine<QuizState>().BySenderId().Advance();
await Reply("Quiz started! Question 1: What is the capital of France?");
return Ok;
}
}
[MessageHandler]
[State<QuizState>(QuizState.Q1)]
public class Q1Handler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
if (Input.Text.Trim().Equals("Paris", StringComparison.InvariantCultureIgnoreCase))
await Reply("Correct!");
else
await Reply("Incorrect. The answer is Paris.");
StateStorage.GetStateMachine<QuizState>().BySenderId().Advance();
await Reply("Question 2: What is 2 + 2?");
return Ok;
}
}
How is it working?
- Enum State Definition:
QuizStateenum defines the conversation flow withStart = SpecialState.NoStateindicating no initial state.- State Filter:
[State<QuizState>(QuizState.Start)]ensures the handler only runs when the user is in the "Start" state.- State Transition:
StateStorage.GetStateMachine<QuizState>().BySenderId().Advance()moves the user to the next state (Q1).- Next Handler: The
Q1Handlerwill only run when the user is in stateQuizState.Q1.- State Management: Each handler manages its own state transition, creating a clear conversation flow.
3.4. Update Routing
The UpdateRouter is the central component that manages how updates flow through your bot.
How Updates Are Processed
- Reception: Updates are received from Telegram via long-polling or webhook
- Filtering: Each registered handler is checked against the update using its filters
- Selection: Handlers that pass all filters are selected for execution
- Prioritization: Selected handlers are sorted by Importance and Priority
- Execution: Handlers are executed in order, with aspects applied
Router Configuration
var options = new TelegratorOptions
{
MaximumParallelWorkingHandlers = 10,
ExclusiveAwaitingHandlerRouting = true,
ExceptIntersectingCommandAliases = true
};
var bot = new TelegratorClient("<BOT_TOKEN>", options);
Error Handling
The router includes built-in error handling:
- Exception Handler: Global exception handler for all errors
- Handler Errors: Individual handler errors are caught and logged
- Recovery: The router continues processing other handlers even if one fails
Performance Considerations
- Parallel Execution: Multiple handlers can run simultaneously
- Concurrency Limits: Control the number of parallel executions
- Resource Management: Automatic cleanup of resources after execution
4. Intermediate Topics
This section covers intermediate concepts that build upon the core concepts.
4.1. Advanced Filters
You can create custom filters by inheriting from FilterAnnotation<T>:
using Telegram.Bot.Types;
using Telegrator.Attributes;
using Telegrator.Handlers;
public class AdminOnlyAttribute : FilterAnnotation<Message>
{
private readonly List<long> _adminIds = [];
public void AddAdmin(long id) => _adminIds.Add(id);
public void RemoveAdmin(long id) => _adminIds.Remove(id);
public override bool CanPass(FilterExecutionContext<Message> context)
=> _adminIds.Contains(context.Input.From?.Id);
}
[MessageHandler]
[AdminOnly]
public class AdminHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Hello, admin!");
return Ok;
}
}
// Usage
AdminOnlyAttribute.AddAdmin(123456789);
bot.StartReceiving();
How is it working?
- Custom Filter:
AdminOnlyAttributeinherits fromFilterAnnotation<Message>to create a reusable filter attribute.- Filter Logic:
CanPass()method checks if the message sender's ID matches the admin ID.- Usage: The filter is applied as an attribute
[AdminOnly]to restrict access to users that are not registered as admins.
4.2. Awaiting Mechanism
Use AwaitingProvider to wait for a user's next update (message or callback) inside a handler:
[CommandHandler]
[CommandAlias("ask")]
public class AskHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("What is your name?");
var nextMessage = await container.AwaitMessage().BySenderId(cancellation);
await Reply($"Hello, {nextMessage.Text}!");
return Ok;
}
}
How is it working?
- Awaiting Provider:
container.AwaitMessage()creates a temporary handler that waits for the next message.- Sender Filter:
.BySenderId(cancellation)ensures only messages from the same user are captured.- Async Flow: The handler pauses execution until the user responds, then continues with the conversation.
- Context Preservation: The original handler context is maintained during the awaiting process.
4.3. Branching Handlers
For complex scenarios where a single handler needs to handle multiple different update types or conditions, you can use BranchingUpdateHandler to create handlers with multiple entry points.
Example:
[MessageHandler]
public class ComplexHandler : BranchingMessageHandler
{
[TextContains("hello")]
public async Task<Result> HandleGreeting(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Hello there!");
return Ok;
}
[TextContains("help")]
public async Task<Result> HandleHelp(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("How can I help you?");
return Ok;
}
[FromUser("John")]
public async Task<Result> HandleAdmin(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Admin command received!");
return Ok;
}
}
Branching Command Handler:
[CommandHandler]
public class SettingsHandler : BranchingCommandHandler
{
[CommandAlias("settings")]
public async Task<Result> ShowSettings(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Settings menu:");
// Show settings options
return Ok;
}
[CommandAlias("settings", "language")]
public async Task<Result> SetLanguage(IHandlerContainer<Message> container, CancellationToken cancellation)
{
var language = Arguments.FirstOrDefault();
await Reply($"Language set to: {language}");
return Ok;
}
[CommandAlias("settings", "theme")]
public async Task<Result> SetTheme(IHandlerContainer<Message> container, CancellationToken cancellation)
{
var theme = Arguments.FirstOrDefault();
await Reply($"Theme set to: {theme}");
return Ok;
}
}
How is it working?
- Multiple Entry Points: Each method with filters becomes a separate handler entry point.
- Individual Filtering: Each method can have its own set of filters and conditions.
- Shared Context: All methods share the same handler instance and context.
- Automatic Registration: Each method is automatically registered as a separate handler.
- Command Arguments: In
BranchingCommandHandler, you can access command arguments via theArgumentsproperty.- Flexible Routing: Perfect for complex bots with many related commands or message patterns.
You can extend Telegrator by creating custom filters, attributes, and state keepers.
4.4. Advanced Hosting Integration
When using Telegrator.Hosting, you can create powerful filters that integrate with your application's services and configuration.
Database-Integrated Filters
public class PremiumUserFilterAttribute : FilterAnnotation<Message>
{
public override bool CanPass(FilterExecutionContext<Message> context)
{
if (context.BotInfo is not HostedTelegramBotInfo botInfo)
return false;
var dbContext = botInfo.Services.GetRequiredService<ApplicationDbContext>();
var user = dbContext.Users
.FirstOrDefault(u => u.TelegramId == context.Input.From?.Id);
return user?.SubscriptionLevel == SubscriptionLevel.Premium;
}
}
// Usage
[MessageHandler]
[PremiumUserFilter]
public class PremiumFeatureHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Welcome to premium features!");
return Ok;
}
}
Configuration-Driven Filters
public class EnvironmentFilterAttribute : FilterAnnotation<Message>
{
private readonly string _environment;
public EnvironmentFilterAttribute(string environment)
{
_environment = environment;
}
public override bool CanPass(FilterExecutionContext<Message> context)
{
if (context.BotInfo is not HostedTelegramBotInfo botInfo)
return false;
var configuration = botInfo.Services.GetRequiredService<IConfiguration>();
var currentEnv = configuration["Environment"] ?? "Production";
return currentEnv.Equals(_environment, StringComparison.OrdinalIgnoreCase);
}
}
// Usage
[MessageHandler]
[EnvironmentFilter("Development")]
public class DevOnlyHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("This feature is only available in development!");
return Ok;
}
}
Multi-Service Integration
public class RateLimitFilterAttribute : FilterAnnotation<Message>
{
public override bool CanPass(FilterExecutionContext<Message> context)
{
if (context.BotInfo is not HostedTelegramBotInfo botInfo)
return false;
var cache = botInfo.Services.GetRequiredService<IDistributedCache>();
var configuration = botInfo.Services.GetRequiredService<IConfiguration>();
var userId = context.Input.From?.Id.ToString();
if (string.IsNullOrEmpty(userId))
return false;
var cacheKey = $"rate_limit:{userId}";
var currentCount = cache.GetString(cacheKey);
if (int.TryParse(currentCount, out int count) && count >= 10)
return false;
cache.SetString(cacheKey, (count + 1).ToString(),
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(1) });
return true;
}
}
// Usage
[MessageHandler]
[RateLimitFilter]
public class RateLimitedHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Message processed!");
return Ok;
}
}
2.6.1. Custom Filter Attributes
Custom Filter Attribute Example:
using Telegram.Bot.Types;
using Telegrator.Attributes;
using Telegrator.Handlers;
public class AdminOnlyAttribute() : FilterAnnotation<Message>
{
private readonly List<long> _adminIds = [];
public void AddAdmin(long id) => _adminIds.Add(id);
public void RemoveAdmin(long id) => _adminIds.Remove(id);
public override bool CanPass(FilterExecutionContext<Message> context)
=> _adminIds.Contains(context.Input.From?.Id);
}
[MessageHandler]
[AdminOnly]
public class AdminHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Hello, admin!");
return Ok;
}
}
// ...
AdminOnlyAttribute.AddAdmin(123456789);
bot.StartReceiving();
How is it working?
- Custom Filter:
AdminOnlyAttributeinherits fromFilterAnnotation<Message>to create a reusable filter attribute.- Filter Logic:
CanPass()method checks if the message sender's ID matches the admin ID.- Usage: The filter is applied as an attribute
[AdminOnly]to restrict access to users that are not registered as admins.
2.6.2. Branching Handlers
For complex scenarios where a single handler needs to handle multiple different update types or conditions, you can use BranchingUpdateHandler to create handlers with multiple entry points.
Example:
[MessageHandler]
public class ComplexHandler : BranchingMessageHandler
{
[TextContains("hello")]
public async Task<Result> HandleGreeting(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Hello there!");
return Ok;
}
[TextContains("help")]
public async Task<Result> HandleHelp(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("How can I help you?");
return Ok;
}
[FromUser("John")]
public async Task<Result> HandleAdmin(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Admin command received!");
return Ok;
}
}
Branching Command Handler:
[CommandHandler]
public class SettingsHandler : BranchingCommandHandler
{
[CommandAlias("settings")]
public async Task<Result> ShowSettings(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Settings menu:");
// Show settings options
return Ok;
}
[CommandAlias("settings", "language")]
public async Task<Result> SetLanguage(IHandlerContainer<Message> container, CancellationToken cancellation)
{
var language = Arguments.FirstOrDefault();
await Reply($"Language set to: {language}");
return Ok;
}
[CommandAlias("settings", "theme")]
public async Task<Result> SetTheme(IHandlerContainer<Message> container, CancellationToken cancellation)
{
var theme = Arguments.FirstOrDefault();
await Reply($"Theme set to: {theme}");
return Ok;
}
}
How is it working?
- Multiple Entry Points: Each method with filters becomes a separate handler entry point.
- Individual Filtering: Each method can have its own set of filters and conditions.
- Shared Context: All methods share the same handler instance and context.
- Automatic Registration: Each method is automatically registered as a separate handler.
- Command Arguments: In
BranchingCommandHandler, you can access command arguments via theArgumentsproperty.- Flexible Routing: Perfect for complex bots with many related commands or message patterns.
2.6.3. Custom Aspects
You can create custom aspects by implementing IPreProcessor or IPostProcessor interfaces to add cross-cutting concerns to your handlers.
Creating a Custom Pre-Processor:
using Telegrator.Aspects;
public class RateLimitProcessor : IPreProcessor
{
private readonly Dictionary<long, DateTime> _lastExecution = new();
private readonly TimeSpan _cooldown = TimeSpan.FromSeconds(5);
public async Task<Result> BeforeExecution(IHandlerContainer container)
{
var userId = container.HandlingUpdate.Message?.From?.Id;
if (userId == null) return Ok;
if (_lastExecution.TryGetValue(userId.Value, out var lastExec))
{
if (DateTime.Now - lastExec < _cooldown)
{
return Result.Fault(); // Stop execution - rate limit exceeded
}
}
_lastExecution[userId.Value] = DateTime.Now;
return Ok;
}
}
Creating a Custom Post-Processor:
using Telegrator.Aspects;
public class MetricsProcessor : IPostProcessor
{
private int _totalExecutions = 0;
private readonly object _lock = new();
public async Task<Result> AfterExecution(IHandlerContainer container)
{
lock (_lock)
{
_totalExecutions++;
}
Console.WriteLine($"Total handler executions: {_totalExecutions}");
return Ok;
}
}
Applying Custom Aspects:
[MessageHandler]
[BeforeExecution<RateLimitProcessor>]
[AfterExecution<MetricsProcessor>]
public class RateLimitedHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Message processed!");
return Ok;
}
}
How is it working?
- Custom Processors: Implement
IPreProcessororIPostProcessorto create reusable aspects- Flow Control: Return
Result.Fault()from pre-processors to stop handler execution- State Management: Processors can maintain their own state for rate limiting, metrics, etc.
- Reusability: Custom aspects can be applied to multiple handlers via attributes
- Separation of Concerns: Business logic remains separate from cross-cutting concerns
2.7. Integration
Telegrator works in console, hosted applications, and ASP.NET Core (webhook) projects.
Console App Example:
var bot = new TelegratorClient("<YOUR_BOT_TOKEN>");
bot.Handlers.CollectHandlersDomainWide();
bot.StartReceiving();
Hosting Example:
using Telegrator.Hosting;
var builder = TelegramBotHost.CreateBuilder();
builder.Handlers.AddHandler<StartHandler>();
var host = builder.Build();
await host.StartAsync();
Web Hosting Example:
using Telegrator.Hosting.Web;
var webOptions = new TelegramBotWebOptions();
var webHost = TelegramBotWebHost.CreateBuilder(webOptions);
webHost.Handlers.AddHandler<StartHandler>();
var host = webHost.Build();
await host.StartAsync();
Note: For web hosting, you need to configure both TelegratorOptions (for bot token) and WebhookerOptions (for webhook settings) in your appsettings.json file.
How is it working?
- Console Integration:
TelegratorClientprovides a simple way to create bots in console applications.- Domain-Wide Collection:
CollectHandlersDomainWide()automatically discovers and registers all handlers in the current assembly.- ASP.NET Core Integration:
TelegramBotHost.CreateBuilder()provides a builder pattern for hosting bots in ASP.NET Core applications.- Webhook Integration:
TelegramBotWebHost.CreateBuilder()provides webhook hosting for production deployments.- Dependency Injection: Handlers and their dependencies are automatically registered with the DI container.
2.8. Aspects & Cross-Cutting Concerns
Telegrator provides a powerful aspect-oriented programming (AOP) system for handling cross-cutting concerns like logging, validation, authorization, and error handling. This system allows you to separate business logic from infrastructure concerns.
Key Concepts:
- Pre-Execution Aspects: Code that runs before handler execution
- Post-Execution Aspects: Code that runs after handler execution
- Self-Processing: Handler implements interfaces directly
- Typed Processing: External processor classes via attributes
Common Use Cases:
- Input validation
- Logging and monitoring
- Authorization and access control
- Error handling and recovery
- Performance metrics collection
- Audit trails
Self-Processing Example
using Telegrator.Aspects;
[MessageHandler]
public class LoggingHandler : MessageHandler, IPreProcessor, IPostProcessor
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Message processed successfully!");
return Ok;
}
public async Task<Result> BeforeExecution(IHandlerContainer container)
{
var user = container.HandlingUpdate.Message?.From;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] User {user?.Id} ({user?.Username}) sent: {container.HandlingUpdate.Message?.Text}");
return Ok;
}
public async Task<Result> AfterExecution(IHandlerContainer container)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Message processing completed");
return Ok;
}
}
Typed Processing Example
using Telegrator.Aspects;
// Validation processor
public class MessageValidationProcessor : IPreProcessor
{
public async Task<Result> BeforeExecution(IHandlerContainer container)
{
var message = container.HandlingUpdate.Message;
if (message?.Text == null)
return Result.Fault(); // Stop execution
if (message.Text.Length > 1000)
return Result.Fault(); // Message too long
return Ok;
}
}
// Logging processor
public class LoggingProcessor : IPostProcessor
{
public async Task<Result> AfterExecution(IHandlerContainer container)
{
Console.WriteLine($"Handler execution completed for update {container.HandlingUpdate.Id}");
return Ok;
}
}
// Handler with external processors
[MessageHandler]
[BeforeExecution<MessageValidationProcessor>]
[AfterExecution<LoggingProcessor>]
public class ValidatedHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Valid message received and processed!");
return Ok;
}
}
Combined Approach Example
using Telegrator.Aspects;
[MessageHandler]
[BeforeExecution<AuthorizationProcessor>]
public class SecureHandler : MessageHandler, IPostProcessor
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Secure operation completed!");
return Ok;
}
// Custom post-processing
public async Task<Result> AfterExecution(IHandlerContainer container)
{
Console.WriteLine($"Secure operation completed for user {container.HandlingUpdate.Message?.From?.Id}");
return Ok;
}
}
How is it working?
- Self-Processing: Handlers implement
IPreProcessorand/orIPostProcessorinterfaces directly- Typed Processing: External processor classes are applied via
[BeforeExecution<T>]and[AfterExecution<T>]attributes- Execution Order: Pre-execution aspects run first, then handler main logic, then post-execution aspects
- Flow Control: Return
Result.Fault()from pre-execution to stop handler execution- Separation of Concerns: Business logic is separated from cross-cutting concerns like logging and validation
3. Step-by-Step Tutorials
3.1. Minimal Bot Creation
3.2. Command Filtering
Message is considered command if is start with '/' and has not null or empty name.
Instead of using the MessageHandler for command (such as /start) you should use CommandHandler.
[CommandHandler]
[CommandAlias("start")]
public class StartHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Welcome! Use /help to see available commands.");
return Ok;
}
}
[CommandHandler]
[CommandAlias("help")]
public class HelpHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Available commands:\n/start - Start the bot\n/help - Show this help");
return Ok;
}
}
[MessageHandler]
[TextStartsWith("/", Modifiers = FilterModifier.Not)]
public class EchoHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply($"You said: \"{Input.Text}\"");
return Ok;
}
}
How is it working?
- Command Handlers:
[CommandHandler]and[CommandAlias]work together to handle specific commands like/startand/help.- Echo Handler:
[TextStartsWith("/", Modifiers = FilterModifier.Not)]catches all messages that don't start with "/" (non-commands).- Handler Separation: Each command has its own dedicated handler, making the code modular and maintainable.
- Filter Modifiers: The
Notmodifier inverts the filter logic to exclude command messages.
3.3. State Management Wizard
[CommandHandler]
[CommandAlias("wizard")]
[NumericState(SpecialState.NoState)]
public class StartWizardHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
container.CreateNumericState(); // This code is not necessary, as "Forward" method can automatically creates state if needed, but its recomended to use
container.ForwardNumericState();
await Reply("What is your name?");
return Ok;
}
}
[MessageHandler]
[NumericState(1)]
public class NameHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
container.ForwardNumericState();
await Reply($"Nice to meet you, {Input.Text}! How old are you?");
return Ok;
}
}
[MessageHandler]
[NumericState(2)]
public class AgeHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
if (int.TryParse(Input.Text, out int age))
{
await Reply($"Thank you! You are {age} years old. Wizard completed!");
container.DeleteNumericState();
}
else
{
await Reply("Please enter a valid age (number).");
}
return Ok;
}
}
How is it working?
- Numeric State:
[NumericState(SpecialState.NoState)]starts the wizard when no state exists.- State Creation:
container.CreateNumericState()initializes the numeric state for the user.- State Progression:
container.ForwardNumericState()moves to the next step (1, then 2).- State Cleanup:
container.DeleteNumericState()removes the state when the wizard completes.- Input Validation: The age handler validates numeric input and provides feedback.
3.4. CallbackQuery Handling
[CommandHandler]
[CommandAlias("menu")]
public class MenuHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
var keyboard = new InlineKeyboardMarkup(new[]
{
InlineKeyboardButton.WithCallbackData("Option 1", "option1"),
InlineKeyboardButton.WithCallbackData("Option 2", "option2")
});
await Reply("Choose an option:", replyMarkup: keyboard, cancellationToken: cancellation);
return Ok;
}
}
[CallbackQueryHandler]
[CallbackData("option1")]
public class Option1Handler : CallbackQueryHandler
{
public override async Task<Result> Execute(IHandlerContainer<CallbackQuery> container, CancellationToken cancellation)
{
await AnswerCallbackQuery("You selected Option 1!", cancellationToken: cancellation);
await EditMessage("You selected Option 1!");
return Ok;
}
}
[CallbackQueryHandler]
[CallbackData("option2")]
public class Option2Handler : CallbackQueryHandler
{
public override async Task<Result> Execute(IHandlerContainer<CallbackQuery> container, CancellationToken cancellation)
{
await AnswerCallbackQuery("You selected Option 2!", cancellationToken: cancellation);
await EditMessage("You selected Option 2!");
return Ok;
}
}
How is it working?
- Inline Keyboard:
InlineKeyboardMarkupcreates interactive buttons withCallbackDataidentifiers.- CallbackQuery Handlers:
[CallbackQueryHandler]and[CallbackData]work together to handle button clicks.- Response Methods:
AnswerCallbackQuery()provides immediate feedback, whileEditMessage()updates the message.- Handler Separation: Each button option has its own dedicated handler for clean code organization.
3.5. Implicit Handlers from Methods
You can create handlers directly from methods without defining full handler classes. This is useful for simple handlers or quick prototyping:
// Simple echo handler
[MessageHandler]
[TextStartsWith("/", Modifiers = FilterModifier.Not)]
private static async Task<Result> EchoHandler(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
await container.Reply($"You said: \"{container.Input.Text}\"", cancellationToken: cancellationToken);
return Ok;
}
// Command handler with inline keyboard
[CommandHandler]
[CommandAlias("menu")]
private static async Task<Result> MenuHandler(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
var keyboard = new InlineKeyboardMarkup(new[]
{
InlineKeyboardButton.WithCallbackData("Option 1", "option1"),
InlineKeyboardButton.WithCallbackData("Option 2", "option2")
});
await container.Reply("Choose an option:", replyMarkup: keyboard, cancellationToken: cancellationToken);
return Ok;
}
// Callback query handler
[CallbackQueryHandler]
[CallbackData("option1")]
private static async Task<Result> Option1Handler(IHandlerContainer<CallbackQuery> container, CancellationToken cancellationToken)
{
await container.AnswerCallbackQuery("You selected Option 1!", cancellationToken: cancellationToken);
await container.EditMessage("You selected Option 1!");
return Ok;
}
// Register all methods as handlers
builder.Handlers.AddMethod<Message>(EchoHandler);
builder.Handlers.AddMethod<Message>(MenuHandler);
builder.Handlers.AddMethod<CallbackQuery>(Option1Handler);
Advanced Example with State Management:
public enum UserState
{
Start = SpecialState.NoState,
WaitingForName,
WaitingForAge
}
// Start conversation
[CommandHandler]
[CommandAlias("register")]
[State<UserState>(UserState.Start)]
private static async Task<Result> StartRegistration(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply("Please enter your name:", cancellationToken: cancellationToken);
return Ok;
}
// Handle name input
[MessageHandler]
[State<UserState>(UserState.WaitingForName)]
private static async Task<Result> HandleName(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
var name = container.Input.Text;
StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply($"Hello {name}! Please enter your age:", cancellationToken: cancellationToken);
return Ok;
}
// Handle age input
[MessageHandler]
[State<UserState>(UserState.WaitingForAge)]
private static async Task<Result> HandleAge(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
if (int.TryParse(container.Input.Text, out int age))
{
container.DeleteEnumState<UserState>();
await container.Reply($"Registration complete! Name: {name}, Age: {age}", cancellationToken: cancellationToken);
}
else
{
await container.Reply("Please enter a valid age (number):", cancellationToken: cancellationToken);
}
return Ok;
}
// Register state management handlers
builder.Handlers.AddMethod<Message>(StartRegistration);
builder.Handlers.AddMethod<Message>(HandleName);
builder.Handlers.AddMethod<Message>(HandleAge);
How is it working?
- Method Signature: Methods must return
Task<Result>and acceptIHandlerContainer<T>andCancellationToken- Attributes: Apply the same filter attributes as regular handlers (
[MessageHandler],[CommandHandler], etc.)- Container Methods: Use extension methods like
container.Reply(),container.Response(), etc.- Registration: Use
AddMethod<T>()to register methods as handlers- State Management: Same state management patterns as regular handlers
- Flexibility: Can be used for simple handlers or complex multi-step conversations
4. Advanced Topics
4.1. Handler Importance & Priority
Telegrator provides two different mechanisms for controlling handler execution order: Importance and Priority. These serve different purposes in the framework's execution model.
Importance (Internal Type Priority)
Importance is an internal parameter used to control priority between different handler types that process the same update type. It's automatically set by the framework based on the handler type.
Built-in Importance Values:
CommandHandler: Importance = 1 (highest priority for message updates)MessageHandler: Importance = 0 (default priority for message updates)CallbackQueryHandler: Importance = 0 (default for callback updates)AnyUpdateHandler: Importance = -1 (lowest priority, catches all updates)
Example of Importance in Action:
[CommandHandler] // Importance = 1 (automatically set)
[CommandAlias("start")]
public class StartHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Command handler executed first!");
return Ok;
}
}
[MessageHandler] // Importance = 0 (automatically set)
[TextContains("hello")]
public class HelloHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Message handler executed second!");
return Ok;
}
}
How is it working?
- Automatic Importance: The framework automatically sets importance based on handler type.
- Command Priority: Commands are processed before regular messages due to higher importance.
- Type-based Ordering: This ensures critical handlers (like commands) run before general handlers.
- Framework Control: Importance is managed internally and shouldn't be manually overridden.
Priority (Global Execution Control)
Priority is a user-controlled parameter that regulates the execution order among all registered handlers in the application, regardless of their type.
Priority Usage:
[MessageHandler(Priority = 10)] // High priority among all handlers
public class HighPriorityHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("This handler runs with high priority!");
return Ok;
}
}
[MessageHandler(Priority = 0)] // Default priority
public class NormalPriorityHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("This handler runs with normal priority!");
return Ok;
}
}
[MessageHandler(Priority = -10)] // Low priority
public class LowPriorityHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("This handler runs with low priority!");
return Ok;
}
}
How is it working?
- Global Priority:
Prioritycontrols execution order across all handler types.- Higher Priority First: Handlers with higher priority numbers run before those with lower numbers.
- User Control: Priority is manually set by developers for custom execution ordering.
- Default Priority: When not specified, handlers have priority 0.
- Cross-Type Ordering: Priority works across different handler types (MessageHandler, CommandHandler, etc.).
Combined Execution Order
The final execution order is determined by both Importance and Priority:
- First: Handlers are sorted by
Importance(type-based priority) - Second: Within the same importance level, handlers are sorted by
Priority(user-defined priority)
Example:
[CommandHandler(Priority = 5)] // Importance = 1, Priority = 5
[CommandAlias("admin")]
public class AdminCommandHandler : CommandHandler
{
// Executes first (highest importance + high priority)
}
[CommandHandler(Priority = 0)] // Importance = 1, Priority = 0
[CommandAlias("start")]
public class StartCommandHandler : CommandHandler
{
// Executes second (highest importance + normal priority)
}
[MessageHandler(Priority = 10)] // Importance = 0, Priority = 10
[TextContains("urgent")]
public class UrgentMessageHandler : MessageHandler
{
// Executes third (normal importance + high priority)
}
[MessageHandler(Priority = 0)] // Importance = 0, Priority = 0
[TextContains("hello")]
public class HelloMessageHandler : MessageHandler
{
// Executes last (normal importance + normal priority)
}
4.2. Dependency Injection (DI)
Telegrator is designed to work seamlessly with DI containers (e.g., ASP.NET Core). Handlers and their dependencies are automatically registered.
[MessageHandler]
public class MyHandler : MessageHandler
{
private readonly IMyService _myService;
private readonly ILogger<MyHandler> _logger;
public MyHandler(IMyService myService, ILogger<MyHandler> logger)
{
_myService = myService;
_logger = logger;
}
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
_logger.LogInformation("MyHandler executed!");
var result = _myService.DoSomething();
await Reply(result);
return Ok;
}
}
How is it working?
- Constructor Injection: Dependencies (
IMyService,ILogger) are automatically injected by the DI container.- Service Registration: When using
Telegrator.Hosting, services are automatically registered with the DI container.- Handler Instantiation: Telegrator creates handler instances through the DI container, resolving all dependencies.
- Logging Integration: Built-in logging support allows for comprehensive debugging and monitoring.
4.3. Custom State Keepers
You can implement your own state keeper by inheriting from StateKeeperBase<TKey, TState>. This allows for advanced scenarios (e.g., per-message state, custom key resolution).
4.4. Automatic Handler Discovery
Telegrator provides automatic discovery and registration of handlers across your entire application domain using the CollectHandlersDomainWide() method.
How it works:
- Scans all loaded assemblies in the current domain
- Automatically discovers classes decorated with handler attributes
- Registers them with the bot without manual registration
Example:
var bot = new TelegratorClient("<YOUR_BOT_TOKEN>");
bot.Handlers.CollectHandlersDomainWide(); // Automatically finds and registers all handlers
bot.StartReceiving();
Benefits:
- No need to manually register each handler
- Reduces boilerplate code
- Ensures all handlers are discovered automatically
- Perfect for large applications with many handlers
How is it working?
- Domain Scanning:
CollectHandlersDomainWide()scans all assemblies loaded in the current AppDomain.- Reflection Discovery: Uses reflection to find all classes decorated with handler attributes.
- Automatic Registration: Each discovered handler is automatically registered with the
HandlersCollection.- Handler Types: Supports all handler types:
MessageHandler,CommandHandler,CallbackQueryHandler, etc.
4.5. Hosting Integration
Note: For automatic handler discovery, you can use bot.Handlers.CollectHandlersDomainWide() instead of manually adding each handler with AddHandler<T>(). This automatically discovers and registers all handlers in the current assembly.
Telegrator provides seamless integration with .NET's generic host through the Telegrator.Hosting package, making it easy to build production-ready bot applications.
Installation:
dotnet add package Telegrator.Hosting
Dependencies:
Microsoft.Extensions.Hosting- .NET Generic HostMicrosoft.Extensions.DependencyInjection- Dependency InjectionMicrosoft.Extensions.Configuration- Configuration managementMicrosoft.Extensions.Logging- Logging infrastructure
Core Components:
TelegramBotHost- The main hosted service that manages the bot lifecycleTelegramBotHostBuilder- Builder pattern for configuring the bot hostTelegramBotOptions- Configuration options for the bot
Basic Example:
var builder = TelegramBotHost.CreateBuilder();
// Configure services
builder.Services.AddSingleton<IMyService, MyService>();
// Configure handlers
builder.Handlers.CollectHandlersDomainWide();
// Building host
var host = builder.Build();
await host.Run();
How is it working?
- Generic Host Integration:
TelegramBotHostimplementsIHostand integrates with .NET's generic host.- Lifecycle Management: The host manages the bot's startup, shutdown, and graceful termination.
- Dependency Injection: All handlers and services are automatically registered with the DI container.
- Configuration: Supports standard .NET configuration patterns (appsettings.json, environment variables, etc.).
- Logging: Integrates with .NET's logging infrastructure for comprehensive monitoring.
- Health Checks: Can be integrated with .NET's health check system for production monitoring.
4.6. Web Hosting (Webhook)
Telegrator provides webhook hosting through the Telegrator.Hosting.Web package for production deployments.
Installation:
dotnet add package Telegrator.Hosting.Web
Dependencies:
Microsoft.AspNetCore.App- ASP.NET CoreMicrosoft.Extensions.Hosting- .NET Generic HostMicrosoft.Extensions.DependencyInjection- Dependency Injection
Core Components:
TelegramBotWebHost- The main web hosted service for webhook handlingTelegramBotWebHostBuilder- Builder pattern for configuring the web hostWebhookerOptions- Configuration options for webhook settings
Configuration Requirements:
TelegratorOptionsmust be configured as it contains the bot tokenWebhookerOptionsmust be configured through external sources (appsettings.json) for webhook settings
Basic Example:
var webOptions = new TelegramBotWebOptions();
var builder = TelegramBotWebHost.CreateBuilder(webOptions);
// Configure services
builder.Services.AddSingleton<IMyService, MyService>();
// Configure handlers
builder.Handlers.CollectHandlersDomainWide();
// Building host
var host = builder.Build();
await host.StartAsync();
Configuration via appsettings.json:
{
"TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN"
},
"WebhookerOptions": {
"WebhookUri": "https://your-domain.com/webhook",
"SecretToken": "your-secret-token",
"MaxConnections": 40,
"DropPendingUpdates": true
},
"HostOptions": {
"ShutdownTimeout": 10,
"BackgroundServiceExceptionBehavior": "StopHost"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Telegrator": "Debug"
}
}
}
How is it working?
- Webhook Integration:
TelegramBotWebHosthandles incoming webhook requests from Telegram.- Security: Supports secret token validation for secure webhook handling.
- Scalability: Webhook hosting is more efficient for high-traffic bots compared to long-polling.
- Production Ready: Includes health checks, logging, and monitoring capabilities.
- SSL Required: Webhook hosting requires HTTPS for production use.
4.7. Configuration Management
The hosting integration provides comprehensive configuration management through standard .NET configuration patterns.
Required Configuration:
The bot token must be configured either in appsettings.json or through environment variables:
{
"TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN"
}
}
Environment Variables: You can also use environment variables for sensitive configuration:
export TelegramBotClientOptions__Token="YOUR_BOT_TOKEN"
Advanced Configuration:
{
"TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN",
"BaseUrl": "https://api.telegram.org"
},
"HostOptions": {
"ShutdownTimeout": 10,
"BackgroundServiceExceptionBehavior": "StopHost"
},
"ReceiverOptions": {
"DropPendingUpdates": true,
"Limit": 10,
"Timeout": 30
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
How is it working?
- Automatic Configuration Binding: The hosting integration automatically binds configuration sections to their respective options classes.
- Environment Variable Support: Configuration can be overridden using environment variables with the
__separator.- Hierarchical Configuration: Supports multiple configuration sources (appsettings.json, environment variables, command line, etc.).
- Type Safety: Configuration is strongly typed and validated at startup.
- Production Ready: Sensitive data like bot tokens can be securely managed through environment variables or secret management systems.
4.8. Error Handling and Logging
You can subscribe to error events or set a custom exception handler:
bot.UpdateRouter.ExceptionHandler = new DefaultRouterExceptionHandler((client, exception, source, cancellationToken) =>
{
Console.WriteLine($"An error occurred: {exception.Message}");
return Task.CompletedTask;
});
Custom Exception Handler:
public class CustomExceptionHandler : IRouterExceptionHandler
{
private readonly ILogger<CustomExceptionHandler> _logger;
public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
{
_logger = logger;
}
public Task HandleException(ITelegramBotClient client, Exception exception, HandleErrorSource source, CancellationToken cancellationToken)
{
_logger.LogError(exception, "Error occurred in {Source}", source);
// You can implement custom error handling logic here
// For example, send error notifications to administrators
return Task.CompletedTask;
}
}
How is it working?
- Exception Handler:
ExceptionHandlerproperty allows you to set a custom exception handler for the entire bot.- Error Context: The handler receives the bot client, exception, source information, and cancellation token.
- Global Error Handling: This provides a centralized way to handle all exceptions that occur during update processing.
- Logging Integration: Perfect place to log errors or send notifications to administrators.
4.9. Performance Optimization
- Use appropriate concurrency limits for resource-intensive operations
- Avoid thread-blocking operations in handlers
- Use state management for multi-step processes
- Use
AwaitingProviderfor complex conversation flows - Consider webhook hosting for high-traffic bots
4.10. Best Practices
- Organize handlers, filters, and state keepers in separate folders
- Use feature modules for large bots
- Prefer declarative filters over manual
ifstatements - Keep handlers focused and single-responsibility
- Use dependency injection for better testability
- Implement proper error handling and logging
- Use webhook hosting for production deployments
Aspect-Oriented Programming Best Practices:
- Separation of Concerns: Keep aspects focused on a single responsibility (logging, validation, authorization, etc.)
- Reusability: Create generic aspects that can be applied to multiple handlers
- Performance: Avoid heavy operations in aspects that run frequently
- Error Handling: Always handle exceptions in aspects gracefully
- Testing: Test aspects independently from handlers
- Documentation: Document the purpose and behavior of custom aspects
- State Management: Use thread-safe collections for aspects that maintain state
Common Aspect Patterns:
- Validation Aspect: Check input data before processing
- Logging Aspect: Record execution details for debugging
- Authorization Aspect: Verify user permissions
- Metrics Aspect: Collect performance and usage data
- Audit Aspect: Track user actions for compliance
- Rate Limiting Aspect: Prevent abuse by limiting request frequency
5. Advanced Topics
This section covers advanced concepts and techniques for building sophisticated bots.
5.1. Aspects & Cross-Cutting Concerns
Telegrator provides a powerful aspect-oriented programming (AOP) system for handling cross-cutting concerns like logging, validation, authorization, and error handling.
Self-Processing Example
using Telegrator.Aspects;
[MessageHandler]
public class LoggingHandler : MessageHandler, IPreProcessor, IPostProcessor
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Message processed successfully!");
return Ok;
}
public async Task<Result> BeforeExecution(IHandlerContainer container)
{
var user = container.HandlingUpdate.Message?.From;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] User {user?.Id} ({user?.Username}) sent: {container.HandlingUpdate.Message?.Text}");
return Ok;
}
public async Task<Result> AfterExecution(IHandlerContainer container)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Message processing completed");
return Ok;
}
}
Typed Processing Example
using Telegrator.Aspects;
// Validation processor
public class MessageValidationProcessor : IPreProcessor
{
public async Task<Result> BeforeExecution(IHandlerContainer container)
{
var message = container.HandlingUpdate.Message;
if (message?.Text == null)
return Result.Fault(); // Stop execution
if (message.Text.Length > 1000)
return Result.Fault(); // Message too long
return Ok;
}
}
// Handler with external processors
[MessageHandler]
[BeforeExecution<MessageValidationProcessor>]
public class ValidatedHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
await Reply("Valid message received and processed!");
return Ok;
}
}
5.2. Custom Extensions
You can extend Telegrator by creating custom filters, aspects, and state keepers.
Custom Filters
public class RateLimitFilter : FilterAnnotation<Message>
{
private readonly Dictionary<long, DateTime> _lastExecution = new();
private readonly TimeSpan _cooldown = TimeSpan.FromSeconds(5);
public override bool CanPass(FilterExecutionContext<Message> context)
{
var userId = context.Input.From?.Id;
if (userId == null) return true;
if (_lastExecution.TryGetValue(userId.Value, out var lastExec))
{
if (DateTime.Now - lastExec < _cooldown)
return false; // Rate limit exceeded
}
_lastExecution[userId.Value] = DateTime.Now;
return true;
}
}
Custom State Keepers
public class CustomStateKeeper : StateKeeperBase<string, string>
{
protected override string GetKey(IHandlerContainer container)
=> container.HandlingUpdate.Message?.From?.Id.ToString() ?? "unknown";
protected override string GetValue(IHandlerContainer container)
=> container.HandlingUpdate.Message?.Text ?? "";
protected override void SetValue(IHandlerContainer container, string value)
{
// Custom state storage logic
}
}
5.3. Performance Optimization
Optimize your bot for high performance:
Concurrency Settings
var options = new TelegratorOptions
{
MaximumParallelWorkingHandlers = 20, // Adjust based on server capabilities
ExclusiveAwaitingHandlerRouting = true,
ExceptIntersectingCommandAliases = true
};
Memory Management
- Use
usingstatements for disposable resources - Implement proper cleanup in custom aspects
- Monitor memory usage in long-running bots
Caching Strategies
public class CachingAspect : IPreProcessor
{
private readonly Dictionary<string, object> _cache = new();
public async Task<Result> BeforeExecution(IHandlerContainer container)
{
var key = container.HandlingUpdate.Message?.Text;
if (_cache.TryGetValue(key, out var cached))
{
// Use cached result
return Ok;
}
return Ok;
}
}
5.4. Error Handling
Implement robust error handling for your bot:
Global Exception Handler
var bot = new TelegratorClient("<BOT_TOKEN>");
bot.ExceptionHandler = new CustomExceptionHandler();
public class CustomExceptionHandler : IRouterExceptionHandler
{
public Task HandleException(ITelegramBotClient client, Exception exception, HandleErrorSource source, CancellationToken cancellationToken)
{
Console.WriteLine($"Error in {source}: {exception.Message}");
return Task.CompletedTask;
}
}
Handler-Level Error Handling
[MessageHandler]
public class SafeHandler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
try
{
// Risky operation
await Reply("Operation completed!");
return Ok;
}
catch (Exception ex)
{
await Reply("Sorry, something went wrong.");
return Result.Fault();
}
}
}
6. Integration & Deployment
6.1. Console Applications
Simple console bot setup:
class Program
{
static void Main(string[] args)
{
var bot = new TelegratorClient("<YOUR_BOT_TOKEN>");
bot.Handlers.CollectHandlersDomainWide();
bot.StartReceiving();
Console.ReadLine();
}
}
6.2. ASP.NET Core Hosting
Host your bot in ASP.NET Core applications:
using Telegrator.Hosting;
var builder = TelegramBotHost.CreateBuilder();
builder.Handlers.AddHandler<StartHandler>();
var host = builder.Build();
await host.StartAsync();
6.3. Webhook Deployment
Deploy your bot using webhooks for production:
using Telegrator.Hosting.Web;
var webOptions = new TelegramBotWebOptions();
var webHost = TelegramBotWebHost.CreateBuilder(webOptions);
webHost.Handlers.AddHandler<StartHandler>();
var host = webHost.Build();
await host.StartAsync();
Configuration (appsettings.json):
{
"TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN"
},
"WebhookerOptions": {
"WebhookUri": "https://your-domain.com/webhook",
"SecretToken": "your-secret-token",
"MaxConnections": 40,
"DropPendingUpdates": true
}
}
6.4. Configuration Management
Manage your bot configuration:
// From appsettings.json
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var botOptions = configuration.GetSection("TelegratorOptions").Get<TelegratorOptions>();
var bot = new TelegratorClient("<BOT_TOKEN>", botOptions);
7. Best Practices & Patterns
7.1. Project Organization
Organize your bot code effectively:
MyBot/
├── Handlers/
│ ├── Commands/
│ │ ├── StartHandler.cs
│ │ └── HelpHandler.cs
│ ├── Messages/
│ │ ├── EchoHandler.cs
│ │ └── GreetingHandler.cs
│ └── Callbacks/
│ └── ButtonHandler.cs
├── Filters/
│ ├── AdminFilter.cs
│ └── RateLimitFilter.cs
├── Aspects/
│ ├── LoggingAspect.cs
│ └── ValidationAspect.cs
├── State/
│ └── QuizState.cs
└── Program.cs
7.2. Testing Strategies
Test your bot components:
[Test]
public async Task StartHandler_ShouldReplyWithWelcome()
{
// Arrange
var handler = new StartHandler();
var container = CreateMockContainer();
// Act
var result = await handler.Execute(container, CancellationToken.None);
// Assert
Assert.That(result.Positive, Is.True);
}
7.3. Common Patterns
Command Pattern
[CommandHandler]
[CommandAlias("settings")]
public class SettingsHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
var keyboard = new InlineKeyboardMarkup(new[]
{
new[] { InlineKeyboardButton.WithCallbackData("Language", "settings_lang") },
new[] { InlineKeyboardButton.WithCallbackData("Theme", "settings_theme") }
});
await Reply("Choose a setting:", replyMarkup: keyboard);
return Ok;
}
}
Wizard Pattern
[CommandHandler]
[CommandAlias("wizard")]
[StringState("no_state")]
public class StartWizardHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
container.ForwardStringState();
await Reply("Step 1: What is your name?");
return Ok;
}
}
7.4. Performance Tips
- Use webhooks for production bots
- Implement caching for frequently accessed data
- Limit concurrent executions based on server capabilities
- Use async/await properly throughout your code
- Monitor memory usage in long-running bots
- Implement proper error handling to prevent crashes
7.5. Logging System
Telegrator provides a centralized logging system called "TelegratorLogging" that allows integration with various logging frameworks while maintaining zero dependencies in the core library.
Overview
The logging system consists of:
- TelegratorLogging - Centralized static logging system
- ITelegratorLogger - Core logging interface
- NullLogger - No-op logger
- ConsoleLogger - Simple console output
- MicrosoftLoggingAdapter - Integration with Microsoft.Extensions.Logging
Basic Usage
Console Logging:
using Telegrator.Logging;
// Add console adapter
TelegratorLogging.AddAdapter(new ConsoleLoggerAdapter(LogLevel.Debug, includeTimestamp: true));
// Use logging
TelegratorLogging.LogInformation("Bot started");
TelegratorLogging.LogError("Something went wrong", exception);
Custom Logger Adapter:
public class CustomLogger : ITelegratorLogger
{
public void Log(LogLevel level, string message, Exception? exception = null)
{
// Your logging implementation
Console.WriteLine($"[{level}] {message}");
if (exception != null)
Console.WriteLine($"Exception: {exception.Message}");
}
}
// Add custom adapter
TelegratorLogging.AddAdapter(new CustomLogger());
Hosting Integration
With Microsoft.Extensions.Logging:
using Telegrator.Hosting.Logging;
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.AddDebug();
});
// Add Microsoft.Extensions.Logging adapter
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger);
TelegratorLogging.AddAdapter(adapter);
var bot = new TelegratorClient("<BOT_TOKEN>");
Log Levels
- Trace - Most detailed logging
- Debug - Detailed debugging information
- Information - General information
- Warning - Warning messages
- Error - Error messages
Simple Logging
All logging methods support simple message logging:
// In your handlers or aspects
TelegratorLogging.LogInformation("Handler executed");
TelegratorLogging.LogError("Something went wrong", exception);
TelegratorLogging.LogWarning("User exceeded rate limit");
Performance Considerations
- TelegratorLogging has minimal overhead with thread-safe adapter management
- NullLogger has zero overhead
- ConsoleLogger is lightweight for development
- MicrosoftLoggingAdapter delegates to the underlying framework
- Simple interface reduces complexity and improves performance
Best Practices
- Configure adapters early in your application startup
- Use ConsoleLogger for development and debugging
- Use MicrosoftLoggingAdapter for ASP.NET Core applications
- Include relevant context in log messages
- Set appropriate log levels based on your needs
- Multiple adapters can be registered for different outputs
8. FAQ & Troubleshooting
8.1. Common Issues
8.1. Q: My handler is not being triggered. What should I do?
- Check handler registration (use
bot.Handlers.AddHandler<MyHandler>()or domain-wide collection) - Check filter attributes and update types
- Enable debug logging
- Verify that the handler class inherits from the correct base class
8.2. Q: How can I access the ITelegramBotClient or the original Update object inside a handler?
- Use
Client,Update, andInputproperties in your handlers container
8.3. Q: How do I handle errors?
- Set a custom exception handler or subscribe to error events
- Use try-catch blocks in individual handlers for specific error handling
- Implement proper logging for debugging
8.4. Q: How can I organize my code for a large bot?
- Use folders, feature modules, and namespaces
- Keep handlers focused and modular
- Use dependency injection for better separation of concerns
- Implement proper state management for complex flows
8.5. Q: What's the difference between Reply() and Responce() methods?
Reply()sends a reply to the original message (with reply markup)Responce()sends a new message to the chat (without reply markup)- Both methods are available in
MessageHandlerandCallbackQueryHandler
Note: Responce() has a typo in the name but is intentionally kept for backward compatibility. Both methods serve different purposes in message handling.
8.6. Q: How do I implement webhook hosting?
- Use
Telegrator.Hosting.Webpackage - Configure
TelegramBotWebOptionswith your bot token and webhook URL - Ensure your server has HTTPS enabled
- Set up proper SSL certificates for production use
8.7. Q: How can I add logging or validation to all handlers?
A: Use the aspect system with IPreProcessor and IPostProcessor interfaces:
- Self-processing: Implement interfaces directly in your handler
- Typed processing: Create external processor classes and apply with attributes
- Combined approach: Use both methods together
This allows you to implement cross-cutting concerns without modifying handler business logic.
8.8. Q: Can I stop handler execution from an aspect?
A: Yes! Return Result.Fault() from a pre-execution processor to stop handler execution. Return Ok to continue.
8.9. Q: What's the execution order of aspects?
A: Aspects execute in the following order:
- Pre-execution aspects (self-processing first, then typed)
- Handler main execution
- Post-execution aspects (self-processing first, then typed)
8.10. Q: How do I create reusable aspects for multiple handlers?
A: Create external processor classes that implement IPreProcessor or IPostProcessor, then apply them using [BeforeExecution<T>] or [AfterExecution<T>] attributes. This allows you to share the same aspect logic across multiple handlers.
8.11. Q: Can I create handlers from methods instead of full classes?
A: Yes! You can create implicit handlers from methods using the same attributes and patterns as regular handlers:
[MessageHandler, TextEquals("Hello", StringComparison.InvariantCultureIgnoreCase)]
private static async Task<Result> HelloWorld(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
await container.Reply("Hello, World!", cancellationToken: cancellationToken);
return Ok;
}
// Register the method as a handler
builder.Handlers.AddMethod<Message>(HelloWorld);
Key Points:
- Method Signature: Must return
Task<Result>and acceptIHandlerContainer<T>andCancellationToken - Attributes: Apply the same filter attributes as regular handlers
- Container Methods: Use extension methods like
container.Reply(),container.Response(), etc. - Registration: Use
AddMethod<T>()to register methods as handlers - Benefits: Quick prototyping, simple handlers, and code reuse
This approach is perfect for simple handlers or when you want to avoid creating full handler classes.
8.12. Q: Can I access DI container and configuration in filters?
A: Yes! When using Telegrator.Hosting, you can access the DI container and configuration in custom filters by casting context.BotInfo to HostedTelegramBotInfo:
public class DatabaseUserFilterAttribute : FilterAnnotation<Message>
{
public override bool CanPass(FilterExecutionContext<Message> context)
{
// Cast to HostedTelegramBotInfo to access services
if (context.BotInfo is not HostedTelegramBotInfo botInfo)
return false;
// Access DI container
var dbContext = botInfo.Services.GetRequiredService<UsersDbContext>();
var configuration = botInfo.Services.GetRequiredService<IConfiguration>();
// Use services in filter logic
var user = dbContext.Users.FirstOrDefault(u => u.TelegramId == context.Input.From?.Id);
return user?.IsActive == true;
}
}
Key Points:
- Hosting Only: This feature is only available when using
Telegrator.Hosting - Type Casting: Cast
context.BotInfotoHostedTelegramBotInfo - Service Access: Use
botInfo.Services.GetRequiredService<T>()to access DI services - Configuration Access: Use
botInfo.Services.GetRequiredService<IConfiguration>()for settings - Null Safety: Always check if the cast is successful before using services
This allows you to create powerful filters that integrate with your application's database, configuration, and other services.
8.8. Q: Can I stop handler execution from an aspect?
A: Yes! Return Result.Fault() from a pre-execution processor to stop handler execution. Return Ok to continue.
8.9. Q: What's the execution order of aspects?
A: Aspects execute in the following order:
- Pre-execution aspects (self-processing first, then typed)
- Handler main execution
- Post-execution aspects (self-processing first, then typed)
8.10. Q: How do I create reusable aspects for multiple handlers?
A: Create external processor classes that implement IPreProcessor or IPostProcessor, then apply them using [BeforeExecution<T>] or [AfterExecution<T>] attributes. This allows you to share the same aspect logic across multiple handlers.
8.11. Q: Can I create handlers from methods instead of full classes?
A: Yes! You can create implicit handlers from methods using the same attributes and patterns as regular handlers:
[MessageHandler, TextEquals("Hello", StringComparison.InvariantCultureIgnoreCase)]
private static async Task<Result> HelloWorld(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
await container.Reply("Hello, World!", cancellationToken: cancellationToken);
return Ok;
}
// Register the method as a handler
builder.Handlers.AddMethod<Message>(HelloWorld);
Key Points:
- Method Signature: Must return
Task<Result>and acceptIHandlerContainer<T>andCancellationToken - Attributes: Apply the same filter attributes as regular handlers
- Container Methods: Use extension methods like
container.Reply(),container.Response(), etc. - Registration: Use
AddMethod<T>()to register methods as handlers - Benefits: Quick prototyping, simple handlers, and code reuse
This approach is perfect for simple handlers or when you want to avoid creating full handler classes.
8.12. Q: Can I access DI container and configuration in filters?
A: Yes! When using Telegrator.Hosting, you can access the DI container and configuration in custom filters by casting context.BotInfo to HostedTelegramBotInfo:
public class DatabaseUserFilterAttribute : FilterAnnotation<Message>
{
public override bool CanPass(FilterExecutionContext<Message> context)
{
// Cast to HostedTelegramBotInfo to access services
if (context.BotInfo is not HostedTelegramBotInfo botInfo)
return false;
// Access DI container
var dbContext = botInfo.Services.GetRequiredService<UsersDbContext>();
var configuration = botInfo.Services.GetRequiredService<IConfiguration>();
// Use services in filter logic
var user = dbContext.Users.FirstOrDefault(u => u.TelegramId == context.Input.From?.Id);
return user?.IsActive == true;
}
}
Key Points:
- Hosting Only: This feature is only available when using
Telegrator.Hosting - Type Casting: Cast
context.BotInfotoHostedTelegramBotInfo - Service Access: Use
botInfo.Services.GetRequiredService<T>()to access DI services - Configuration Access: Use
botInfo.Services.GetRequiredService<IConfiguration>()for settings - Null Safety: Always check if the cast is successful before using services
This allows you to create powerful filters that integrate with your application's database, configuration, and other services.
8.2. Debugging Guide
Enable Debug Logging
For detailed information about the logging system, see section 7.5 - Logging System.
Quick Start:
using Telegrator.Logging;
// Add console adapter for debugging
TelegratorLogging.AddConsoleAdapter(LogLevel.Debug, includeTimestamp: true);
// Use logging
TelegratorLogging.LogInformation("Bot started");
TelegratorLogging.LogError("Something went wrong", exception);
Common Debugging Steps
- Check Handler Registration: Verify handlers are properly registered
- Verify Filters: Ensure filters are correctly configured
- Test Individual Handlers: Test handlers in isolation
- Monitor Logs: Check application logs for errors
- Use Breakpoints: Set breakpoints in handler methods
Performance Monitoring
- Monitor handler execution times
- Check memory usage patterns
- Track concurrent execution counts
- Monitor error rates and types
Simple Logging
// In your handlers or aspects
TelegratorLogging.LogInformation("Handler executed");
TelegratorLogging.LogError("Something went wrong", exception);
TelegratorLogging.LogWarning("User exceeded rate limit");
9. Links
Feel free to contribute, ask questions, or open issues!
В главных ролях :
Сишарпилло Крокодилло, Дыкий Сишарп, Шарпенко Михаил Дотнетович
Кастинг и Тестирование :
не проводилось