Propagate states to dialogs in MS BotFramework - 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>();

Related

How to share a context between bots?

I'm using the Microsoft bot framework in order to implement a simple bot-to-bot communication scenario. In my case, I have a master-bot and a skill-bot. I have completed their integration so that the master dot can pass a conversation to the skill and the skill can continue.
I need to share some state between bots. So, I've created a state property accessor:
public static readonly string UserContextPropertyName = $"{typeof(RootBot<T>).FullName}.UserContextProperty";
private readonly IStatePropertyAccessor<PatientResponce> _userContext;
and use it in the OnTurnAsync method
await _userContext.SetAsync(turnContext, patientResponce);
All good, except this new property is not available in the OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) turnContext of the Skill
Why it does not persist/pass-through?
P.S. I do save the state before I pass a conversation to the skill by calling:
await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken);
P.S.S I also use
var userState = new UserState(new BlobsStorage("..."))
services.AddSingleton(userState);
in Startup.cs as a storage model
OK, there is a workaround but it is ok for my case - maybe someone finds it useful: there is a property
turnContext.Activity.Value
seem like it's been designed for attachments; it has no defined type - it is just an Object.
So what I did is just store my context there as a serialized json string and catch it up on the other side. I consider this as a sub-optimal solution - so beware of boxing-undoxing overhead if you go that way

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.

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

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;
}

What's the best way of handling ViewModel destroying for CancellationToken activation with MvvmCross?

I am having an MvvmCross ViewModel, which calls different async methods of my DataService.
Similar to the following:
public class LoginViewModel : MvxViewModel
{
private readonly IIdentityService _dataService;
private CancellationTokenSource _viewModelCancellationTokenSource;
public IMvxCommand GoLogin { get; set; }
public LoginViewModel(IIdentityService identityService)
{
_dataService = identityService;
_viewModelCancellationTokenSource = new CancellationTokenSource();
GoLogin = new MvxCommand(async () => await ProcessLogin());
}
private async Task ProcessLogin()
{
// calling the dataservice which must stop processing
// (to cancel) in case if the ViewModel is being destroyed
await _dataService.AssureIsLoggedIn(data, _viewModelCancellationTokenSource.Token);
await NavigationService.Navigate<LoginNextStepViewModel>();
}
public override void ViewDestroy(bool viewFinishing = true)
{
base.ViewDestroy(viewFinishing);
// not sure if that is a right (and working) place
_viewModelCancellationTokenSource.Cancel();
}
}
So, MvvmCross is quite unclear about the part with the ViewModel destroying. It describes Construction, Init, Reload and Start, but doesn't say any definite regarding the destroying:
Monitoring other View/ViewModel lifecycle event across multiple
platforms is fairly tricky, especially once developers start
experimenting beyond the ‘basic’ presentation models and start using
tabs, splitviews, popups, flyouts, etc
For most viewmodels, it’s common to not try to monitor other lifecyle
events. This is OK since most viewmodels don’t perform any actions and
don’t consume any resources when the view is not present - so these
can just be left to be garbage collected when the system needs the
memory back.
However, besides the custom platform situations, there are still many cases like navigating back from the view model, or (again) navigation away from current viewmodel with its following closing.
So, what's the best way to handle it then?
From your code:
// calling the dataservice which must stop processing
// (to cancel) in case if the ViewModel is being destroyed
The ViewModel won't be destroyed before the async methods finish executing. I think you are confusing the View with the ViewModel.
In case of a login page, you would usually prevent the user from navigating away from it until your server call goes through.
If for some reason you want to cancel then you need to decide what scenarios you want to handle, there is no single universal place. Your options are the view callbacks:
void ViewDisappearing();
void ViewDisappeared();
void ViewDestroy();
and the navigation events:
event BeforeNavigateEventHandler BeforeNavigate;
event AfterNavigateEventHandler AfterNavigate;
event BeforeCloseEventHandler BeforeClose;
event AfterCloseEventHandler AfterClose;
event BeforeChangePresentationEventHandler BeforeChangePresentation;
event AfterChangePresentationEventHandler AfterChangePresentation;

GWT - Handling events from underlying widgets in EntryPoint

I have searched the web for the correct answer, but I've been failing to achieve this :
In EntryPoint class, I need to manage widgets according to events that occur in nested widgets. I've cleaned of the code to focus only on what is important here.
I have built a few UiBinder widgets, for example, a Login pane where the user can enter his credentials. In my EntryPoint class, I add the widgets in the correct position.
// This is from EntryPoint class
public void onModuleLoad() {
LoginPane lp = new LoginPane();
RootPanel.get("headerRightPane").add(lp);
lp.setFocus();
// Other widgets added in same manner after this point...
}
I would like a successful login to remove the LoginPane and replace it by another widget (AccountPane) that would show the account information for the user that is logged in. I have an onClick event, in LoginPane, that sends a request to a fully functional Servlet that checks the credentials. At this exact point, if the Servlet determines that the login is indeed successful, I would like to fire a "successfulLogin" event (from LoginPane) that could notify the EntryPoint class that the LoginPane can now be replaced by the AccountPane.
// This is from LoginPane class
#UiHandler("loginButton")
void onClick(ClickEvent e) {
checkCredentials(usernameField.getText(), passwordField.getText());
}
public void checkCredentials(String username, String password) {
String usernameToServer = username;
String passwordToServer = password;
credentialsService.credentialsServer(usernameToServer, passwordToServer,
new AsyncCallback<CredentialsPaneContent>() {
public void onFailure(Throwable caught) {
answerLabel.setText(Utilities.SERVER_ERROR);
}
public void onSuccess(CredentialsPaneContent result) {
if ( result == null ) {
answerLabel.setText("Login Failed.");
} else {
// Fire event here (to be caught by EntryPoint class)
answerLabel.setText("Login Successful.");
}
}
});
}
So, the question : How should I proceed to create, fire and listen to the event from my nested widget?
Use an EventBus. Additionally, consider adopting the Model-View-Presenter pattern to keep your application maintainable as it grows:
Large scale application development and MVP, Part I
Large scale application development and MVP, Part II
GWT MVP Development with Activities and Places
Lets have an interface which is implemented by EntryPoint class,
now have a referrence of interface type which actually an object of interface.
Using this interface referrence invoke the listner(interface) mothod, which serves your purpose.

Resources