Skip to content

Handlers And Routing

Handlers are the main user-facing part of TeleFlow. A handler is a normal class with one or more methods decorated by routing attributes.

Command Handler

public sealed class StartHandler
{
    [Command("start")]
    public Task Handle(MessageContext ctx, CancellationToken ct)
    {
        return ctx.Message.AnswerAsync("Welcome.", ct);
    }
}

[Command("start")] matches /start by default. The command prefix can be changed through attribute properties.

Text Handler

public sealed class MenuHandler
{
    [Text("Settings")]
    public Task Settings(MessageContext ctx, CancellationToken ct)
    {
        return ctx.Message.AnswerAsync("Opening settings.", ct);
    }
}

[Text] supports match modes through TextMatchMode.

[Text("support", TextMatchMode.Contains, ignoreCase: true)]
public Task ContainsSupport(MessageContext ctx, CancellationToken ct)
{
    return ctx.Message.AnswerAsync("Support request detected.", ct);
}

Template Routes

Templates are useful when the message has a stable structure:

[CommandTemplate("ticket {id:long}")]
public Task ShowTicket(MessageContext ctx, long id, CancellationToken ct)
{
    return ctx.Message.AnswerAsync($"Ticket #{id}", ct);
}

Text templates work without a command prefix:

[TextTemplate("order {id:long}")]
public Task ShowOrder(MessageContext ctx, long id, CancellationToken ct)
{
    return ctx.Message.AnswerAsync($"Order #{id}", ct);
}

Regex Routes

Use regex routes when the input shape is not well represented by a template:

[TextRegex(@"^INV-(\d+)$")]
public Task Invoice(MessageContext ctx, CancellationToken ct)
{
    return ctx.Message.AnswerAsync("Invoice code received.", ct);
}

Regex routes are powerful, but templates are usually easier to read and maintain.

Media Filters

TeleFlow includes marker attributes for common message content:

public sealed class UploadHandler
{
    [HasPhoto]
    public Task Photo(MessageContext ctx, CancellationToken ct)
    {
        return ctx.Message.AnswerAsync("Photo received.", ct);
    }

    [HasDocument]
    public Task Document(MessageContext ctx, CancellationToken ct)
    {
        return ctx.Message.AnswerAsync("Document received.", ct);
    }
}

Other media filters include audio, voice, video, video note, animation, contact, dice, location, poll, sticker, venue, caption, and message thread markers.

Routes and filters both use C# attribute syntax. Route attributes such as [CommandTemplate] select the handler and bind values. Filter attributes such as [HasPhoto], [ChatType], and [UseFilter<TFilter>] add conditions before invocation. See Filters for the full distinction.

Class-Level Metadata

Routing and filter attributes can be placed on the class when every method should share the same condition:

[ChatType(TelegramChatType.Private)]
public sealed class PrivateChatHandlers
{
    [Command("profile")]
    public Task Profile(MessageContext ctx, CancellationToken ct)
    {
        return ctx.Message.AnswerAsync("Private profile.", ct);
    }
}

Handler Parameters

Handler methods can receive:

  • the current context;
  • route values from templates;
  • typed callback payloads;
  • CancellationToken;
  • services from dependency injection.
public sealed class TicketHandler
{
    [CommandTemplate("ticket {id:long}")]
    public Task Handle(
        MessageContext ctx,
        long id,
        ITicketRepository tickets,
        CancellationToken ct)
    {
        return ctx.Message.AnswerAsync($"Ticket #{id}", ct);
    }
}

Keep method signatures readable. If the handler needs many services, move application logic into a service and inject that service.

Direct Registration

Direct registration is simple and explicit:

builder.Services.AddTelegramHandler<StartHandler>();
builder.Services.AddTelegramModule<AdminHandlers>();

Use it for small apps, tests, or narrow modules.

Assembly Registration

Generated assembly registration is the recommended default for larger applications:

builder.Services.AddTelegramHandlersFromAssembly(typeof(Program).Assembly);

This requires IWF.TeleFlow.Generators. Missing generated metadata is treated as a startup configuration error.