Search code examples
c#botframeworkchatbotdirect-line-botframework

Start conversation with a PromptDialog in MS BotFramework


Is there a proper way for bot to start a conversation with PromptDialog.Choice in the Direct Line channel?

I am trying an ugly hack with catching the fist ConversationUpdate activity and creating a fake message from user to initialize the dialog like this:

IMessageActivity greetingMessage = Activity.CreateMessageActivity();
greetingMessage.From = message.Recipient;//from bot
greetingMessage.Recipient = userAccount;//to user
greetingMessage.Conversation = message.Conversation;
greetingMessage.Text = "Hello, I am a bot";
greetingMessage.Locale = "en-us";
greetingMessage.Id = Guid.NewGuid().ToString();

await connector.Conversations.SendToConversationAsync((Activity)greetingMessage);

IMessageActivity dialogEntryMessage = Activity.CreateMessageActivity();
dialogEntryMessage.Recipient = message.Recipient;//to bot
dialogEntryMessage.From = message.From;//from user
dialogEntryMessage.Conversation = message.Conversation;
dialogEntryMessage.Text = "any text";
dialogEntryMessage.Locale = "en-us";
dialogEntryMessage.ChannelId = message.ChannelId;
dialogEntryMessage.ServiceUrl = message.ServiceUrl;
dialogEntryMessage.Id = Guid.NewGuid().ToString();
dialogEntryMessage.ReplyToId = greetingMessage.Id;

await Conversation.SendAsync(dialogEntryMessage, () => new Dialogs.RootDialog());

Where message is a ConversationUpdate message from. In the RootDialog I start with a PromptDialog.Choice.

It works in the emulator, but in Direct Line channel bot doesn't remember the dialog state and when user choose one of dialog options and send his first real message, root dialog starts again from the PromptDialog.Choice, so it appears twice.

Update

I found a relevant blogpost from Microsoft: https://blog.botframework.com/2018/07/12/how-to-properly-send-a-greeting-message-and-common-issues-from-customers/


Solution

  • in Direct Line channel bot doesn't remember the dialog state and when user choose one of dialog options and send his first real message, root dialog starts again from the PromptDialog.Choice, so it appears twice.

    I can reproduce same issue on my side, and I find that ConversationUpdate handler will be executed when both bot and user is added to the conversation.

    To solve the issue, you can refer to the following code sample.

    In MessagesController:

    else if (message.Type == ActivityTypes.ConversationUpdate)
    {
        // Handle conversation state changes, like members being added and removed
        // Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
        // Not available in all channels
    
        if (update.MembersAdded != null && update.MembersAdded.Any())
        {
            foreach (var newMember in update.MembersAdded)
            {
                if (newMember.Name == "{your_botid_here}")
                {
                    IMessageActivity greetingMessage = Activity.CreateMessageActivity();
    
                    //...
                    //your code logic
                    //...
    
                    IMessageActivity dialogEntryMessage = Activity.CreateMessageActivity();
                    dialogEntryMessage.Recipient = message.Recipient;//to bot
                    dialogEntryMessage.From = message.From;//from user
                    dialogEntryMessage.Conversation = message.Conversation;
                    dialogEntryMessage.Text = "show choices";
                    dialogEntryMessage.Locale = "en-us";
                    dialogEntryMessage.ChannelId = message.ChannelId;
                    dialogEntryMessage.ServiceUrl = message.ServiceUrl;
                    dialogEntryMessage.Id = System.Guid.NewGuid().ToString();
                    dialogEntryMessage.ReplyToId = greetingMessage.Id;
    
                    await Conversation.SendAsync(dialogEntryMessage, () => new Dialogs.RootDialog());
                }
            }
        }
    }
    

    In RootDialog:

    private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
    {
        var activity = await result as Activity;
    
        var mes = activity.Text.ToLower();
    
        string[] choices = new string[] { "choice 1", "choice 2" };
    
        if (Array.IndexOf(choices, mes) > -1)
        {
            await context.PostAsync($"You selected {mes}");
        }
        else if(mes == "show choices")
        {
            PromptDialog.Choice(context, resumeAfterPrompt, choices, "please choose an option.");
        }
        else
        {
            await context.PostAsync($"You sent {activity.Text} which was {length} characters.");
            context.Wait(MessageReceivedAsync);
        }
    
    }
    
    private async Task resumeAfterPrompt(IDialogContext context, IAwaitable<string> result)
    {
        string choice = await result;
    
        await context.PostAsync($"You selected {choice}");
    }
    

    Test result:

    enter image description here