Aspect-Oriented Programming (AOP) in Telegrator allows you to add cross-cutting concerns—like logging, authorization, validation, metrics, and error handling—to your handlers without cluttering their core logic.
using Telegrator.Aspects;using Telegrator.Handlers.Components;public class MetricsPostProcessor : IPostProcessor{ private readonly IMetricsService _metrics; public MetricsPostProcessor(IMetricsService metrics) { _metrics = metrics; } public async Task<Result> AfterExecution( IHandlerContainer container, CancellationToken cancellationToken) { // Calculate execution time if (container.ExtraData.TryGetValue("processingStartTime", out var startObj)) { var startTime = (DateTime)startObj; var duration = DateTime.UtcNow - startTime; _metrics.RecordHandlerDuration( container.GetType().Name, duration.TotalMilliseconds); } // Record update type var updateType = container.HandlingUpdate.Type; _metrics.IncrementUpdateCounter(updateType.ToString()); return Result.Ok(); }}
public class AdminOnlyPreProcessor : IPreProcessor{ private readonly IUserService _userService; public AdminOnlyPreProcessor(IUserService userService) { _userService = userService; } public async Task<Result> BeforeExecution( IHandlerContainer container, CancellationToken cancellationToken = default) { var userId = container.HandlingUpdate.Message?.From?.Id ?? 0; if (userId == 0) { return Result.Fail("No user ID found"); } bool isAdmin = await _userService.IsAdminAsync(userId, cancellationToken); if (!isAdmin) { // Short-circuit execution - handler won't run await container.Client.SendTextMessageAsync( container.HandlingUpdate.Message!.Chat.Id, "⛔ You don't have permission to use this command.", cancellationToken: cancellationToken); return Result.Fail("Unauthorized"); } // User is admin, continue to handler return Result.Ok(); }}// Usage[CommandHandler][CommandAllias("admin")][BeforeExecution<AdminOnlyPreProcessor>]public class AdminCommandHandler : CommandHandler{ public override async Task<Result> Execute( IHandlerContainer<Message> container, CancellationToken cancellation) { // This only runs if user is an admin await Reply("Admin panel opened.", cancellationToken: cancellation); return Result.Ok(); }}
When a pre-processor returns Result.Fail(), the handler’s Execute method will not run.
This allows you to short-circuit execution for authorization, validation, etc.
public class ErrorHandlingPostProcessor : IPostProcessor{ private readonly ILogger<ErrorHandlingPostProcessor> _logger; public ErrorHandlingPostProcessor(ILogger<ErrorHandlingPostProcessor> logger) { _logger = logger; } public async Task<Result> AfterExecution( IHandlerContainer container, CancellationToken cancellationToken) { // Check if handler execution had errors if (container.ExtraData.TryGetValue("handlerException", out var exceptionObj)) { var exception = exceptionObj as Exception; var userId = container.HandlingUpdate.Message?.From?.Id ?? 0; _logger.LogError( exception, "Handler failed for user {UserId}", userId); // Notify user await container.Client.SendTextMessageAsync( container.HandlingUpdate.Message!.Chat.Id, "An error occurred. Our team has been notified.", cancellationToken: cancellationToken); // Could also send alert to monitoring system // await _alertingService.SendAlertAsync(exception); } return Result.Ok(); }}
Processors support dependency injection when using Telegrator.Hosting:
// Startup.cs or Program.csservices.AddSingleton<IMetricsService, MetricsService>();services.AddSingleton<IUserService, UserService>();services.AddTransient<LoggingPreProcessor>();services.AddTransient<MetricsPostProcessor>();services.AddTransient<AdminOnlyPreProcessor>();// Processors will be resolved from DI container automatically