State и wizard¶
State позволяет боту помнить, где пользователь находится в conversation. TeleFlow разделяет state name, state data и wizard history.
Регистрация state storage¶
Для local development:
Это регистрирует:
IStateStore;IStateDataStore;IStateDataSerializer;IStateHistoryStore;- state middleware.
Memory storage process-local. Перед multi-instance production deployment его нужно заменить.
Простой state flow¶
public static class RegistrationStates
{
public const string Name = "registration:name";
public const string Age = "registration:age";
}
public sealed class RegistrationHandlers
{
[Command("register")]
public async Task Start(MessageContext ctx, CancellationToken ct)
{
await ctx.State.SetAsync(RegistrationStates.Name, ct);
await ctx.Message.AnswerAsync("What is your name?", ct);
}
[State(RegistrationStates.Name)]
[HasText]
public async Task Name(MessageContext ctx, CancellationToken ct)
{
await ctx.State.Data.SetAsync("name", ctx.TelegramMessage.Text!, ct);
await ctx.State.SetAsync(RegistrationStates.Age, ct);
await ctx.Message.AnswerAsync("How old are you?", ct);
}
[State(RegistrationStates.Age)]
[HasText]
public async Task Age(MessageContext ctx, CancellationToken ct)
{
var name = await ctx.State.Data.GetRequiredAsync<string>("name", ct);
await ctx.State.ResetAsync(ct);
await ctx.Message.AnswerAsync($"Registered: {name}, {ctx.TelegramMessage.Text}", ct);
}
}
Используй ResetAsync, когда нужно очистить и state, и state data.
State API¶
ctx.State разделяет current state value и state data:
| API | Поведение |
|---|---|
GetAsync() |
Читает current state или возвращает null. |
SetAsync(string) / SetAsync(State) |
Сохраняет current state. |
IsAsync(State) |
Читает current state и сравнивает его с typed state value. |
ClearAsync() |
Очищает только current state value. |
ResetAsync() |
Сначала очищает state data, затем current state. |
ctx.State кэширует snapshot текущего state, синхронизированный со storage, на время одного update. Первый GetAsync читает storage, следующие current-state reads в том же update используют snapshot, а успешные SetAsync или ClearAsync обновляют его. Failed storage calls snapshot не меняют. Прямые записи через IStateStore внутри того же update находятся вне этого synchronization path; в handlers и middleware используй ctx.State.
ctx.State.Data хранит небольшие JSON-serialized values по string key:
| API | Поведение |
|---|---|
GetAsync<T>(key) |
Возвращает default, если key отсутствует. |
GetRequiredAsync<T>(key) |
Падает, если key отсутствует или deserializes to null. |
SetAsync<T>(key, value) |
Сохраняет non-null value. |
RemoveAsync(key) |
Удаляет одно значение. |
ClearAsync() |
Очищает все data для текущего state key. |
ctx.State.Data доступен только когда зарегистрированы state data storage и serializer. AddMemoryStateStorage() регистрирует оба.
Wizard navigation¶
Wizard добавляет navigation history:
public static class TicketStates
{
public static readonly State Category = State.Create("ticket:category");
public static readonly State Description = State.Create("ticket:description");
public static readonly State Confirm = State.Create("ticket:confirm");
}
public sealed class TicketWizard
{
[Command("ticket")]
public async Task Start(MessageContext ctx, CancellationToken ct)
{
await ctx.Wizard.GoToAsync(TicketStates.Category, ct);
await ctx.Message.AnswerAsync("Choose category.", ct);
}
[State("ticket:category")]
[HasText]
public async Task Category(MessageContext ctx, CancellationToken ct)
{
await ctx.Wizard.Data.SetAsync("category", ctx.TelegramMessage.Text!, ct);
await ctx.Wizard.GoToAsync(TicketStates.Description, ct);
await ctx.Message.AnswerAsync("Describe the issue.", ct);
}
[State("ticket:description")]
[Text("back")]
public async Task Back(MessageContext ctx, CancellationToken ct)
{
await ctx.Wizard.BackAsync(ct);
await ctx.Message.AnswerAsync("Back to previous step.", ct);
}
}
BackAsync требует state history storage. AddMemoryStateStorage() его регистрирует.
Wizard API:
| API | Поведение |
|---|---|
GetCurrentAsync() |
Читает storage при необходимости, синхронизирует current state snapshot и возвращает current wizard state или null. |
Current |
Синхронно возвращает уже синхронизированный current state snapshot; не делает storage I/O и падает, если snapshot ещё не загружен или active state отсутствует. |
GoToAsync(state) |
Устанавливает следующий state и кладёт previous state в history, если он был. |
BackAsync() |
Восстанавливает previous state; падает, если history пустой. |
ResetAsync() |
Очищает state data, history и current state. |
Scenes¶
Scenes дают canonical state prefix и named steps:
[Scene("ticket")]
public sealed class TicketScene
{
public static State Category { get; } = State.Create("ticket:category");
[Command("ticket")]
public async Task Start(MessageContext ctx, CancellationToken ct)
{
await ctx.Wizard.GoToAsync(Category, ct);
await ctx.Message.AnswerAsync("Choose category.", ct);
}
[SceneStep(nameof(Category))]
[HasText]
public Task CategoryStep(MessageContext ctx, CancellationToken ct)
{
return ctx.Message.AnswerAsync("Category saved.", ct);
}
}
Scenes полезны, когда flow растёт и state names нужна стабильная структура.
State key¶
Telegram state keys создаются из Telegram context. Для message и callback flows state остаётся scoped to user and chat. Advanced applications могут заменить IStateKeyFactory.
Custom keys нужны, когда business requirements требуют tenant-aware, bot-aware или cross-chat state partitioning.