Skip to content

Waiting for user interaction

Mikhail edited this page Jan 10, 2021 · 5 revisions

Waiting for user interaction

Framework contains Sessions support, which serves for asynchronously waiting for user interaction (sending message, clicking the inline button).

For example, your bot should do this:

  • Bot sends "Hello, please write your name:" and waits for user message in the current chat, or anywhere else (you could specify this at the checking time).
  • User sends "John".
  • Bot receives message, and writes "John" into the variable.

How to implement that in the simple way?

Firstly, you should add session handler to the start of the processing pipeline and register ISessionManager in the ContainerBuilder, and your code should be like that:

All classes and definitions are taken from the Getting Started page.

Registering session manager:

containerBuilder.RegisterSessionManager();

Configuring processing pipeline:

botBuilder.WithPositionedUpdateEntityProcessorBuilder<Message>(
    MessagesEntityProcessorId,
    UpdateType.Message,
    updateEntityProcessorBuilder => updateEntityProcessorBuilder
        .WithSessionRequestUpdateEntityHandler(new MessageEntityHandlerAttribute(0))
        .WithUpdateEntityHandlersFromType(typeof(CheckMessageTextForHelloHandlerSource));

Then, lets change some code in our CheckMessageTextForHelloHandler message handler:

[MessageEntityHandlerAttribute(position: 1)]
public async Task<bool> CheckMessageTextForHelloHandler(IHandlerContext<Message> messageHandlerContext,
    ITextMatcherService textMatcherService)
{
    ISession<Message>
        session = messageHandlerContext.Session(); //Retrieving session, related to the current user and update type.

    int maxAttempts = 10; //Set max attempts for passing our "check"

    ISessionUpdateEntityRequestResult<Message> messageRequestResult = await session.RequestEntityAsync(
        new SessionUpdateEntityRequestOptions<Message>()
        {
            Action = async state =>
            {
                int availableAttempts = maxAttempts - state.CurrentAttempt;

                await messageHandlerContext.BotClient.SendTextMessageAsync(messageHandlerContext.Entity.Chat,
                    $"Hello, you have {availableAttempts} attempts left, please write your name:");
            },
            Matcher = state => textMatcherService.CheckThatMessageTextMatchSomeCriteria(state.LastEntity.Text),
            Attempts = maxAttempts
        });

    if (messageRequestResult.IsSuccess)
    {
        Message receivedMessage = messageRequestResult.Entity;

        await messageHandlerContext.BotClient.SendTextMessageAsync(messageHandlerContext.Entity.Chat,
            $"Congrats! You've passed the check! Your text was: {receivedMessage.Text}.");
    }
    else
    {
        await messageHandlerContext.BotClient.SendTextMessageAsync(messageHandlerContext.Entity.Chat,
            "Fail! Oops, seems like your messages didn't contain required text for passing the check :(");
    }

    return messageRequestResult.IsSuccess;
}

What does this code do?

  • Bot sends "Hello, you have 10 attempts left, please write your name:" at the start.
  • Asynchronously waits for the user message which should contain "hello" text.
  • If received message contains "hello", returns this message to the our handler (messageRequestResult will contain this message in the Entity property). Otherwise, performs steps 1-3 again, until condition would be true, or until current attempt would equal to max attemps count.
  • Depending on the message request result, sends message to the user which indicates our "check" success or fail.

And, how our sessions works under the hood?

Default implementation of ISession<TEntity> implies using System.Threading.Channels package, which implies Producer-Consumer pattern using. Basically, when RequestEntityAsync was called, session's flag (IsUpdateEntityRequested), which indicates, should incoming TEntity object be routed to our session, and then to our handler or command where RequestEntityAsync was called, will be set to true, and when our session handler (which we previously added by calling WithSessionRequestUpdateEntityHandler(...) method) will be invoked, it will see, that message for the current user and update type, should be routed into our session.

Routing is performed in that way:

  • RequestEntityAsync invokes Action, then waits until entity channel will have at least one object.
  • When entity was successfully wrote to the channel, waiting will be completed and Matcher will be called, to check that entity matches all requirements.
  • If check was successfull, returns entity. Otherwise, performs steps described above until Matcher result will be true, or attempts count would exceed max attempts count.

Sessions using also shown in the example here.