Wait for Even type Activity in a waterfallstep dialog (bot framework 4.0) - botframework

It's possible to wait and receive an Event type activity in a waterfall step dialog. I use directline 3.0 and inside a dialog flow I send an event from the bot to the client. After i would like to send an event from the client to the bot as an answer to previous send. If i use prompt await dc.Prompt("waitEvent",activity) where waitEvent is a textprompt and i answer with a message it works fine but I would like to answer to an event with an event. I was thinking that i could write a custom prompt but i didn't find documentation and obviously I could manage the conversation flow but I prefer use Dialogs where possible

You can use the ActivityPrompt abstract class to build an "EventActivityPrompt" class.
There aren't any BotFramework samples of this usage yet, but there are new tests written by the BotFramework team that you can use as an example.
To create your own EventActivityPrompt, you just need to implement the ActivityPrompt like so:
public class EventActivityPrompt : ActivityPrompt
{
public EventActivityPrompt(string dialogId, PromptValidator<Activity> validator)
: base(dialogId, validator)
{
}
}
The core difference between an ActivityPrompt and other Prompts (besides its abstract status) is that ActivityPrompts require a PromptValidator<Activity>, in order to validate the user input.
The next step is to create your validator. Here is the example:
async Task<bool> _validator(PromptValidatorContext<Activity> promptContext, CancellationToken cancellationToken)
{
var activity = promptContext.Recognized.Value;
if (activity.Type == ActivityTypes.Event)
{
if ((int)activity.Value == 2)
{
promptContext.Recognized.Value = MessageFactory.Text(activity.Value.ToString());
return true;
}
}
else
{
await promptContext.Context.SendActivityAsync("Please send an 'event'-type Activity with a value of 2.");
}
return false;
}

Related

TextPrompt reprompts after interruption

TL;DR
When my bot is waiting in a Prompt (e.g. TextPrompt) and another dialog ends (because user input triggered an interrupt action such as 'help', which started an help dialog that just outputs help text), the OnPromptAsync method of that Prompt is called and prompts the Prompts text again. I don't want this. I want the Prompt dialog to wait for user input after the help dialog has ended.
Detailed
I have a bot that prompts something using TextPrompt and then waits for the user to reply. I've implemented user interruptions as described here to catch requests for help. If the user typed in 'help' the bot should output some help text (in the ExampleDialog, see below) and then again wait for user inputs.
The MainDialog
public class MainDialog : ComponentDialog
{
public MainDialog() : base("Main")
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new ExampleDialog(nameof(ExampleDialog)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
PromptStep,
EvaluationStep
}));
InitialDialogId = nameof(WaterfallDialog);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
if (!string.IsNullOrEmpty(innerDc.Context.Activity.Text))
{
// Check for interruptions
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
private async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken)
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message)
{
string text = innerDc.Context.Activity.Text;
// Catch request for help
if (text == "help")
{
await innerDc.BeginDialogAsync(nameof(ExampleDialog), null, cancellationToken);
return Dialog.EndOfTurn;
}
}
return null;
}
private async Task<DialogTurnResult> PromptStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter some text. Type 'help' if you need some examples."),
});
}
private async Task<DialogTurnResult> EvaluationStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("You typed: " + stepContext.Result as string));
return await stepContext.EndDialogAsync(null, cancellationToken);
}
}
The ExampleDialog
public class ExampleDialog : ComponentDialog
{
public ExampleDialog(string dialogId) : base(dialogId)
{
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ExampleStep
}));
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> ExampleStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Example: bla bla"));
return await stepContext.NextAsync(null, cancellationToken);
// ExampleDialog ends here
}
}
The problem is, that when the ExampleDialog ends after outputting the help text, the TextPrompt resumes and again prompts its message. This results in this conversation:
Bot: Hello world!
Bot: Please enter some text. Type ‘help’ if you need some examples.
User: help
Bot: Example: bla bla
Bot: Please enter some text. Type ‘help’ if you need some examples.
I don't want this last line to be reprompted by the bot. How can I fix this?
Thanks in advance
EDIT 1: A (not really satisfying) workaround
I've found a solution which does not really satisfy me. I've created my own TextPrompt class called MyTextPrompt and overwritten ResumeDialogAsync:
public class MyTextPrompt : TextPrompt
{
public MyTextPrompt(string id) : base(id)
{
}
public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext dc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
{
return Dialog.EndOfTurn;
}
}
In MainDialog I simply replaced TextPrompt with MyTextPrompt in the constructor
AddDialog(new MyTextPrompt(nameof(MyTextPrompt)));
and use the correct dialog id in the PromptStep
private async Task<DialogTurnResult> PromptStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(MyTextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter some text. Type 'help' if you need some examples."),
});
}
The result is this conversation:
Bot: Hello world!
Bot: Please enter some text. Type ‘help’ if you need some examples.
User: help
Bot: Example: bla bla
/* bot now waits at this point of the conversation */
User: bla bla
Bot: You typed: bla bla
Ok, great. This is what I wanted, isn't it?
Yes it is, but there are some drawbacks:
This has to be done for every single type of prompt dialog.
If you've already overwritten the ResumeDialogAsync method in a custom prompt class, in some way you have to keep track what causes the call of ResumeDailogAsync.
How can this be solved in an elegant way?
I can think of three major possibilities for getting a prompt to not reprompt when it's resumed.
1. Nullify the prompt
The only way to actually stop the built-in prompts from reprompting when they're resumed is to set the Prompt property to null. I mentioned in the comments that there has been some question about whether resuming a prompt should count as retrying a prompt, and you happen to be in luck because resuming a prompt does not count as a retry. This means you can leave the RetryPrompt property intact so that it still automatically reprompts on invalid input like normal. All you have to take care of is making the initial prompt show up without a prompt property, and that's easy enough because you can just send a message that's worded like a prompt before you call the actual prompt.
2. Use middleware
Even if the prompt tries to reprompt, it does so through the turn context and so the activity it tries to send to the user will pass through the middleware pipeline. This means you can catch the activity in custom middleware and prevent it from actually getting sent to the user.
In order for your custom middleware to recognize the prompt activity as something it should catch, the activity will need some kind of tag in one of its properties that the middleware is programmed to look for. Activities have a lot of hidden metadata that you could use for something like this, like ChannelData or Entities or Value or Properties. You won't want the middleware to block the activity all the time because most of the time you'll actually want the prompt to be displayed, so there also needs to be a switch in the turn state that you can activate in the help dialog and that the middleware can check for.
In order for your custom middleware to actually stop the activity from going through, you can either short-circuit the pipeline or you can replace the activity with something that won't show up in the conversation.
3. Create a wrapper class
This possibility is based on the idea you already came up with, but instead of deriving from every existing prompt you can just make one class that can wrap any prompt. I won't make the whole implementation for you but the class signature might look something like this:
class NonResumingPrompt<TPrompt, TValue> : Prompt<TValue> where TPrompt : Prompt<TValue>
You could instantiate NonResumingPrompt like this for example:
new NonResumingPrompt<TextPrompt, string>()
Then the code could internally create an instance of TPrompt and define NonResumingPrompt.OnPromptAsync, NonResumingPrompt.OnRecognizeAsync, and NonResumingPrompt.OnPreBubbleEventAsync to call the corresponding methods in the wrapped TPrompt instance.
This would solve the problem of having to make your own version of every prompt, but it would require you to override a few more methods than in your solution. I should mention that making your own version of each prompt is not so different from what happened when the adaptive dialogs library was made. New "adaptive" versions (called inputs) of each prompt were made, and a lot of code ended up getting duplicated unfortunately. You can feel free to try out adaptive dialogs if you like.

Propagate states to dialogs in MS BotFramework

In Microsoft BotFramework v4 you normally propagate the states (UserState, ConversationState, PrivateConversationState) to a dialog by passing them as parameters to its constructor.
This way:
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
IStorage storage = new MemoryStorage(); // For testing only !
services.AddSingleton(new UserState(storage));
services.AddSingleton(new ConversationState(storage));
// ...
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
services.AddSingleton<GoviiBaseDialog>(x => new RootDialog(
x.GetRequiredService<UserState>(),
x.GetRequiredService<ConversationState>()
);
services.AddTransient<IBot, Bot<RootDialog>>();
}
}
Bot.cs
public class Bot<T> : ActivityHandler where T : Dialog
{
T _dialog;
BotState _userState, _conversationState;
public Bot(T dialog, UserState userState, ConversationState conversationState,)
{
_userState = userState;
_conversationState = conversationState;
_dialog = dialog;
}
public override async Task OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default)
{
await base.OnTurnAsync(context, cancellationToken);
await _userState.SaveChangesAsync(context);
await _conversationState.SaveChangesAsync(context);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> context, CancellationToken cancellationToken)
{
await _dialog.RunAsync(context, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
RootDialog.cs
public class RootDialog : ComponentDialog
{
UserState _userState;
ConversationState _conversationState;
public RootDialog(UserState uState, ConversationState cState) : base("id")
{
_userState = uState;
_conversationState = cState;
// Add some dialogs and pass states as parameters
AddDialog(new CustomDialog_1(uState, cState));
AddDialog(new CustomDialog_2(uState, cState));
// ...
AddDialog(new CustomDialog_N(uState, cState));
}
}
Now let's assume that those CustomDialogs again uses some other CustomDialogs which needs to access the state. The states have to be passed again and again as parameters to the constructors.
The question is: Is there another way to access the states to avoid passing them again and again as parameters?
How you access state in a dialog will depend on the scope of the state.
If the state is scoped to the dialog then you should be using dialog state. More specifically, you should be using the state property of the dialog's associated dialog instance. There's some discussion of that in a recent answer here: Dialogs keep their variable values in new conversation in MS BotFramework v4
(Read about dialog instances here). Anything you want your dialog to keep track of, you should put in the associated dialog instance's state object, and the best place to see examples of how to do that is in the SDK source code itself. For example, you can see how a waterfall dialog keeps track of things like its custom values and what step it's on:
// Update persisted step index
var state = dc.ActiveDialog.State;
state[StepIndex] = index;
If the state has a greater scope than one instance of one dialog, you can pass the bot state objects to your dialogs like you've been doing. This can potentially be made easier if you put the dialogs in dependency injection so that your bot state can be automatically injected into their constructors. If your dialogs access state properties that are also used outside of the dialogs then it makes sense to give the dialogs a state property accessor instead of the state itself, reducing redundancy and separating concerns.
If you want to make sure your bot state is accessible anywhere you have a turn context, there's actually a built-in way to automatically add your bot state to turn state every turn. This is with the UseBotState extension method:
adapter.UseBotState(userState, conversationState);
You can then retrieve the state like this:
var userState = turnContext.TurnState.Get<UserState>();
var conversationState = turnContext.TurnState.Get<ConversationState>();

How to read state property accessors outside the dialog in V4 Bot Framework

I'm using bot framework version 4. I would like to access user state properties in the validator method but I didn't find any solution to it.
GitHub
In the GitHub sample above, we have a validator AgePromptValidatorAsync which validates age.
But I would want to access Name which I have stored in State property.
How could that be achieved.
And is it possible to access state/use GetAsync in a method outside dialog which doesn't contain context.
#mdrichardson could you please help me in this.Thank you in advance.
1. Ensure that UserProfile.Name is saved before hitting validation.
That sample doesn't do this on it's own, so you would:
private async Task<DialogTurnResult> NameConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["name"] = (string)stepContext.Result;
// ADDED: This code block saves the Name
if (!string.IsNullOrEmpty((string)stepContext.Result)) {
var userProfile = await _userProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);
userProfile.Name = (string)stepContext.Result;
await _userProfileAccessor.SetAsync(stepContext.Context, userProfile);
}
// We can send messages to the user at any point in the WaterfallStep.
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Thanks {stepContext.Result}."), cancellationToken);
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Would you like to give your age?") }, cancellationToken);
}
2. Access the User Profile
// CHANGED: Since this accesses the userProfile, the method is no longer static. Also must be async
private async Task<bool> AgePromptValidatorAsync(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken)
{
// ADDED: Here is how you can access the Name
// Note: You can use promptContext.Context instead of stepContext.Context since they're both ITurnContext
var userProfile = await _userProfileAccessor.GetAsync(promptContext.Context, () => new UserProfile(), cancellationToken);
var name = userProfile.Name;
// Do whatever you want with the Name
// CHANGED: Since this is now async, we don't return Task.FromResult(). We just return the result
return promptContext.Recognized.Succeeded && promptContext.Recognized.Value > 0 && promptContext.Recognized.Value < 150;
}
Accessing the UserProfile Without Context
This is kind of possible, but you can't do this easily or out-of-the-box. There are some options that you can use, however (mostly in order from least difficult to most):
Pass the context to whatever method/function you need to use it in. Just about every bot method you'd use has some kind of context that you can pass into another method. This is definitely your best option.
Create a separate class that you use to store variables in bot memory
Either Write Directly to Storage or Implement Custom Storage that you use to track the UserProfile. Note, you'd have to pass around your Storage object, so you may as well just pass around the context, instead.
Use the new Adaptive Dialogs, since they do state management differently. I highly recommend against this, though, as these are "experimental", meaning that there's still bugs and we barely use this internally. I'm adding this as an option more for posterity and users that want to play with new stuff.

How to remove tooManyAttempts message in Prompt.Choice? How to accept text in Prompt.Choice that is not in list of options? C#

I'm using bot-Framework SDK3 C#.
I want to allow user input anything which is not in "PromptDialog.Choice"'s options. Any better ways to recommend?
This is my code.
private async Task SelectCategory(IDialogContext context)
{
List<string> options = new List<string>();
options = category.Keys.ToList();
options.Add("Category1");
options.Add("Category2");
options.Add("Category3");
PromptOptions<string> promptOptions = new PromptOptions<string>(
prompt: "which one do you prefer?",
tooManyAttempts: "",
options: options,
attempts: 0);
PromptDialog.Choice(context: context, resume: ResumeAfterSelectCategory, promptOptions: promptOptions);
await Task.FromResult<object>(null);
}
private async Task ResumeAfterSelectCategory(IDialogContext context, IAwaitable<string> result)
{
try
{
selected = await result;
}
catch (Exception)
{
// if the user's input is not in the select options, it will come here
}
}
But the problem is it always send the message "tooManyAttempts". If I set it to empty, I will send me "0".
I suppose you are using NodeJS. You can use the simple builder.Prompts.choice with maxRetries set to 0. Here is a sample snippet. It asks user to choose some option from a list, or they can enter something which is not in the list.
If you are using C# SDK, you can find some similar option for the list.
bot.dialog("optionalList", [
function(session){
builder.Prompts.choice(
session,
"Click any button or type something",
["option1", "option2", "option3"],
{maxRetries: 0} // setting maxRetries to zero causes no implicit checking
)
},
function(session, result){
// something from the list has been clicked
if(result.response && result.response.entity){
console.log(result.response.entity); // use the clicked button
} else {
console.log(session.message.text) // if user entered something which is not in the list
}
}
]);
EDIT 1:
Hi, Saw that you are using C# SDK. I am not that proficient with that but I can give you some suggestion.
The list which you generate in the async task SelectCategory you can generate in some other place, which is also accessible to the second async task ResumeAfterSelectCategory, (like making it a class variable or getting from database).
Now that the list is accessible in the 2nd task, you can compare what user has typed against the list to determine if the message is from the list or not.
If message is something from the list, then take action accordingly, otherwise user has entered something which is not in the list, and then take action accordingly.
Your 2nd problem is
And if user typed, it will show a message "you tried to many times"
What is meant by that? Does bot sends "you tried to many times" to the bot visitor. In which case it could be the behavior of library. You will be able to control that only if library provides some option. Else I don't know. Hope, that helps
EDIT 2:
I came across this SO question Can I add custom logic to a Bot Framework PromptDialog for handling invalid answers?
You can use that questions answer. Basically extending PromptDialog.PromptChoice<T>.Here is an example.
Override TryParse method like this
protected override bool TryParse(IMessageActivity message, out T result)
{
bool fromList = base.TryParse(message, out result);
if (fromList)
{
return true;
} else {
// do something here
return true; // signal that parsing was correct
}
}
I used node.js and to get message which user entered. use this code snippet.
(session, args) => {
builder.Prompts.text(session, "Please Enter your name.");
},
(session, args) => {
session.dialogData.username = args.response;
session.send(`Your user name is `${session.dialogData.username}`);
}

How to use Messaging center in xamarin forms

I am trying to use messaging center instead of Messenger in xamarin forms I have no idea about messaging center I tried Bellow code to subscribe and Send Message in xamarin forms
MessagingCenter.Send(this, "TodoTable", "Todo");
But I have not Idea from where I can subscribe to this message I tried bellow code :
MessagingCenter.Subscribe<TodoClass>(this, Todo, async (sender, arg) =>
{
await RefreshCommand.ExecuteAsync();
});
This is giving me error Any Help will appreciated :)
It is a quirk of XF messaging centre that (it seems) you need to know who will be sending the message, and potentially who will be receiving it.
However it can be object. The signature of subscribe is:
void MessagingCenter.Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender sender = null)
The trick is to subscribe thus:
MessagingCenter.Subscribe<object>(this, "messageName", Action<object> callback)
This says object or anything derived from object can be a sender, ie, everything.
However if you want to only subscribe to messages sent by a particular instance of a type:
MessagingCenter.Subscribe<MyClass>(this, "messageName", Action<MyClass> callback)
The use of the full signature is a bit suspect. Basically it is saying only if sent from the source object are subscribers who used that source object when subscribing.
MessagingCenter.Subscribe<object, string>(this, "Hi",
(sender, arg) =>
{
DisplayAlert("Message Received", "arg=" + arg, "OK");
},
BindingContext);
if you use the following to send the message it wont be received by the subscriber just above:
MessagingCenter.Send<object, string>(this, "Hi", "John");
But the following will be received
MessagingCenter.Send<object, string>(BindingContext, "Hi", "John");
Though why would you want to send a message to yourself. (Assuming the subscribe and send were in the same page in this case).
However if there were multiple pages with the exact same binding context the message will be sent to all such subscribers. Eg, pages bound to the same view model.
To improve the answer by #user2825546, if you wish to subscribe to only messages from your view-models, you need to specify the base class type when sending the message:
MessagingCenter.Subscribe<BaseViewModel, string>(this, "ShowError", (view, message) => { });
public class StartupViewModel : BaseViewModel
{
//Like this...
MessagingCenter.Send<BaseViewModel, string>(this, "ShowError", "Message");
//Or...
MessagingCenter.Send((BaseViewModel)this, "ShowError", "Message");
}
When testing, I tried to send the message as StartupViewModel, but the listener was not receiving the messages. I guessed that it would, since the class derives from the BaseViewModel.
Send Method
MessagingCenter.Send<Application>(Application.Current,"RefreshDocs");
Subscribe Method
MessagingCenter.Subscribe<Application>(Application.Current , "RefreshDocs" , (sender) =>
{
});
The goal of MVVM is to abstract away your Views from your Business Logic. This ensures great code reuse, testability, and is pretty awesome. Many MVVM Frameworks offer tools to enhance this such as data binding and dependency services to make our lives easier. These are built into Xamarin.Forms, which is awesome, but one feature less talked about is the Messaging Center. It’s entire goal is to enable ViewModels or other components to communicate with each other without having to know anything about each other besides a simple Message contract.
So for instance, let’s say you are in a master/detail setup where your MasterViewModel has a list of items and your DetailViewModel allows you to create a new item, update an item, or delete an item. When your user is on the detail page and triggers an event you need to somehow message back to your MasterViewModel that has a list of Items so the UI can react on the Master page when we navigate back.
So let’s say our MasterViewModel subscribes to “Update” and “AddNew” message events. It will then update it’s observable collection based on when it receives messages. Our DetailViewModel would then send a message in our SaveCommand to notify anyone that is subscribed to these specific messages:
public ObservableCollection<TripExpense> Expenses { get; set; }
public ExpensesViewModel()
{
Expenses = new ObservableCollection<TripExpense>();
//Subscibe to insert expenses
MessagingCenter.Subscribe<TripExpense>(this, "AddNew", (expense) =>
{
Expenses.Add(expense);
});
//subscribe to update expenxes
MessagingCenter.Subscribe<TripExpense>(this, "Update", (expense) =>
{
ExecuteUpdateExpense(expense);
});
}
private async Task ExecuteSaveCommand()
{
if (IsBusy)
return;
IsBusy = true;
//Send a message to insert/update the expense to all subscribers
if(isNew)
{
MessagingCenter.Send(expense, "AddNew");
}
else
{
MessagingCenter.Send(expense, "Update");
}
IsBusy = false;
navigation.PopAsync();
}
There you have it, messaging made easy! Don’t forget to unsubscribe if you no longer wish to receive notifications.

Resources