How to call custom dialogs in MessageController for custom events - c#

Problem statement:
I want to call dialogs from MessageController when custom events are sent to the bot.
Setup:
I have a bot build using Microsoft Bot Framework [v3.15.3]
I have a set of custom events that are sent to the bot from external systems to notify the bot adn ask it to perform actions.
[ Example, MarkUserAsOfflineInBackEndStore ShowExternalActionCompletedMessageToUser]
My users connect to the bot using a web portal that has a webchat connect. This also sends custom events to the bot to notify the bot about user actions
[Example UserClickedOnLogoutFromSite , userNavigatedToDifferentPage]
For these events also the bot has to take some actions.
Problem Statement:
From my message controller I have to redirect to different dialog based on different events that come in.
My current setup is below:
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
if (activity.Type == ActivityTypes.Event)
{
var eventDialog = GetEventDialog(scope, activity);
if (eventDialog != null)
{
await Conversation.SendAsync(activity, () => eventDialog).ConfigureAwait(false);
}
}
else
{
await Conversation.SendAsync(activity, () => scope.Resolve<RootDialog>()).ConfigureAwait(false);
}
}
I want the user ot be able to talk to the bot without error irrespective of the events that are happening. This means that If the bot is waiting for user input using context.Wait(...) the events should not cause unexpected behavior.
I explored calling dialogs using context.Call but could not find a recommended way to get the context reference in messagecontroller.
Looking for suggestion on how to setup the code here.
In my basic scenario,when i send messages one at a time and one event at a time with no waits, it seems to be working correctly. But with complex dialog i get "Sorry my bot code is having an error".

Your issue seems to be based on a common misunderstanding of the MakeRoot delegate in Conversation.SendAsync. The delegate cannot be used to control which dialog the activity is sent to. The reason you have to pass a delegate to begin with instead of just a dialog is because in most cases no new dialog should be constructed. The idea behind Conversation.SendAsync is to send the activity to whatever dialog is on top of the stack. From the documentation:
The MakeRoot factory method is invoked for new conversations only, because existing conversations have the dialog stack and state serialized in the IMessageActivity data.
While I don't know the details of what you're trying to do, I presume you should be able to respond to most events in a way that does not require the use of a dialog. If you're sure you want to send those events to a dialog, you need to keep in mind that there's only one dialog stack and only one dialog on top of the stack, which means you'd need to make sure all of your dialogs are able to handle all possible events gracefully. Please have a look at the documentation to better understand dialogs and dialog flow: https://learn.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-manage-conversation-flow?view=azure-bot-service-3.0

Related

Slack interactive message not triggering Post request on my bot

I'm working on a bot using the Microsoft Bot Framework. I'm trying to get it up and running on Slack, and got blocked by an issue regarding interactive buttons. In a waterfall dialog flow, I'm creating a choice prompt like below
private async Task<DialogTurnResult> IdentifyUserTypeAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var message = "Please select what kind of user you are"
userTypes = await userTypeRepository.GetAllUserTypesAsync();
var options = new PromptOptions
{
Prompt = MessageFactory.Text(message),
Choices = ChoiceFactory.ToChoices(userTypes.Select(x => x.UserType.ToString()).ToList()),
Style = ListStyle.HeroCard,
};
await state.ConversationState.SaveChangesAsync(stepContext.Context);
return await stepContext.PromptAsync(UserTypeDialogId, options);
}
On the Slack app, the buttons renders correctly, like in the image below.
However, when I click the buttons, nothing happens. I see the request on my local slack client actually calls my slack app, and returns with a 200.
I have followed this tutorial on setting up the Slack app using ABS:
https://learn.microsoft.com/en-us/azure/bot-service/bot-service-channel-connect-slack?view=azure-bot-service-4.0&tabs=abs
On the slack app I have the following configurations
Redirect URL:
Scopes:
Events are enabled, and I have added the request URL. The black part is my bot handle, taken from ABS
I have subscribed the following Bot events
The bot is configured to be always online:
And lastly, I have enabled interactivity, and added the request URL, which seemed to be validated correctly by the Slack app.
Am I missing something? I have tried to start over multiple times, but I end up in the same situation, where the button doesn't seem to fire anything on my bot code. There's simply no incomming request to the Bot's webserver.
A fix is on the way. In the meantime, there are two workarounds you can try. You can create a classic Slack app, or you can bypass the ABS Slack connector by using the Slack adapter instead.
You can see a discussion of this here: https://github.com/microsoft/BotBuilder-Samples/issues/2553

ShowTypingMiddleware sending typing activities after response is sent to user by the bot

I have created a v4 Bot Framework SDK (C#) based bot. And I am using the two middleware inside the adapter which are in same order -
public AdapterWithErrorHandler(ICredentialProvider credentialProvider,
BotConfigurationSettings configuration,
IUtility utility,
BotStateSet botStateSet,
ConversationState conversationState = null)
: base(credentialProvider)
{
// Show Typing Middleware
Use(new ShowTypingMiddleware(1000, 2000));
// Auto Save State Middleware
Use(new AutoSaveStateMiddleware(botStateSet));
}
The problem is that typing middleware send the typing activity sometimes intermittently and sometimes more frequently after a message is sent to the user and displayed in bot window.
But the side effect of this behavior is that it looks like bot typing another response but then typing indicator disappears.
This is very well reflected when I see that Dependency Call is cancelled in Application Insights.
I have tried to add custom delay and period values, but noting much happens.
Use(new ShowTypingMiddleware(1000, 2000));
I assume the reason might be AutoSaveStateMiddleware is trying to save conversation state and typing middleware is supposed to send activity after a period of delay but soon state is saved and then typing activity is cancelled.
I want to know how we can fix this behaviour.

How can I have my bot start in a different dialog depending on user

Basically, I want to have my bot on different pages using iframes and Bot Framework Web Chat.
Depending on what page the user opens the bot, a different dialog is called.
Without Direct Line, this is as simple as adding a query parameter to api/messages and adjust the controller. But with Direct Line, I can only specify one endpoint in the portal.
I tried to change user.id in the JavaScript BotChat.App call to a different identifier and then select the correct dialog in my MessagesController based on that.
BUT: my custom id doesn't seem to be available as early as the activity "ConversationUpdate" occures where I send my welcome message. I need to send a dialog-specific welcome message though (so I need to navigate there as soon as possible and not only when the user types his first message).
Take a look at the backchannel. In your scenario you can pass a parameter from the Javascript via the back channel to set some value in UserData. Now your parameter will exist in state (in this case the IDataBag UserData) when your user first hits the bot.
Here is a C# sample of a 2-way backchannel
another C# sample using a 1 way backchannel

Proactive messaging with prompt buttons - Microsoft Bot Framework

I've been playing around with proactive messaging, but I'm not sure how to achieve something like this. I've got a service that messages users products on a daily basis. Using a resumption cookie I call an end point, build a response by setting the .Text property and create the carousel of products by populating the .Attachments property. I want to add buttons to this message to allow a user to stop the alerts, but the only event a CardAction button allows is opening an URL in a web browser. Is there a way of creating a button which can trigger a method, or call a new dialog?
Any help will be greatly appreciated.
You can just use the ImBack or PostBack ActionTypes on the CardAction. Clicking on a button when using any of those, will send a message to the conversation with the Value of the action that will end up arriving to the method you are "waiting" (by doing context.Wait(...));
Then, in that method you can do whatever you want: call a new dialog or any other logic you want to trigger.
In the ContosoFlowers sample you can see how a card with buttons is used in that way.

Microsoft Bot Framework dialog with multiple users

I am using the Microsoft Bot Framework for .NET to create a simple question/answer bot, as an ASP.NET MVC WebApi, written in C#, that will be used primarily on Slack channels with multiple users.
I have created a dialog which I initiate from my message controller as per the framework documentation:
await Conversation.SendAsync(activity, () => new QuestionDialog());
The lambda expression indicates to the Bot Framework which constructor to use for a dialog. QuestionDialog implements IDialog. After a dialog is initiated the dialog class is serialized and stored in the Bot Connector cloud, saving the current question and expected answer, then deserialized whenever a new message is received in the current conversation.
I want any user to be able to answer a random question presented by the Bot regardless of which user initiated the dialog but it seems that a new QuestionDialog is created for each user interacting with the bot so only the user initiating the question can answer it.
Is there any way of creating a dialog that is tied to the conversation/channel only rather than the combination of conversation/channel and user? Does it even make sense to attempt using a dialog in this way?
I guess the way I would implement this is to:
Store the state (the latest question) for each of the channels separately. For example, create a serializable class "QuestionState" which has at least the following properties: Service URL, conversation account ID and the question itself.
For each incoming activity (message), check the service URL and the conversation account ID of the sender and compare that against your list of states. If you find a match, evaluate the message (answer) in respect to the latest question. If there are no matches, this is a channel without previous state, so you need to create one and ask the first question.
Azure Table storage is a handy service to store the states in. You can, of course, use any service you like. Just keep in mind that in-memory state storage only works for testing.
In addition, you don't have necessarily have to use dialogs for this solution at all - you can do all you need to do in the MessagesController.

Categories

Resources