How to share a context between bots? - botframework

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

Related

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.

MessageReceivedAsync calls without argument

I have this very basic question about calls to MessageReceivedAsync. I understand this method is called from context.Wait. However, what I want to clarify is how is the function called without passing on any arguments.
The method definition has 2 arguments.
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
}
Rahul,
this is actually a somewhat complicated question. I'll try to explain as best I can and point you to the code you can examine to get a deeper understanding if you desire.
context.Wait(MessageReceivedAsync) is calling the Wait method of the IDialogContext which is defined as..
public static void Wait(this IDialogStack stack, ResumeAfter<IMessageActivity> resume)
As you can see, this is an extension method of IDialogStack. The important thing to see here is the second parameter ResumeAfter. ResumeAfter is a delgate for what to do when the Wait event occurs, which is usually someone typing a new message to your bot.
Ok, now we can look at the definition of the delegate ResumeAfter. It is defined as...
public delegate Task ResumeAfter<in T>(IDialogContext context, IAwaitable<T> result);
and there's your answer. The parameters for MessageReceivedAsync are a result of the delegate ResumeAfter. The values of the parameters are defined by and setup by the bot framework.
I hope this gave you a better understanding of what's happening behind the scenes with a MS bot.
This code is all contained on GitHub in Microsoft's BotBuilder source
The specific code file I'm references is IDialogContext.cs located here.

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;

RegisterInstance can't register with IntstancePerRequestMode in Autofac

I have a code in Web Api Delegating Handler that extract data from request header.
However, I can't register instance in Autofac container because Autofac container require SingleInstance only.
public class ExtractUserNameMessageHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
var userNameFromFrontEnd = request.GetDependencyScope().GetService(typeof (IUserNameFromFrontEnd));
if (userNameFromFrontEnd == null)
{
var updatedContainerBuilder = new ContainerBuilder();
userNameFromFrontEnd = ExtractUserName(request);
if (userNameFromFrontEnd == null)
{
throw new Exception("We've got a request without UserName header");
}
updatedContainerBuilder.RegisterInstance(userNameFromFrontEnd)
.As<IUserNameFromFrontEnd>()
.InstancePerRequest();
var autofacDependencyResolver = GlobalConfiguration.Configuration.DependencyResolver as AutofacWebApiDependencyResolver;
if (autofacDependencyResolver == null)
{
throw new Exception("We can work with Autofac DI container");
}
updatedContainerBuilder.Update(autofacDependencyResolver.Container as IContainer);
}
When I try to update container I get an exception with message - registration can support singleinstance() sharing only.
What does it mean? I can't understand why we have this limitation. But in any cases my first goal - update container with new dependency.
Does anybody have ideas?
(Note: This question was cross-posted to the Autofac forums as well.)
When you register a specific instance, it's effectively a singleton - it's one instance, the instance you provided.
When you try to assign it InstancePerRequest or, really, any other lifetime scope besides SingleInstance, it doesn't make logical sense because you're not going to get a different instance per request (or whatever). You're going to get the exact same instance you registered, which is a singleton.
The exception message is trying to tell you how to avoid incorrect expectations: that it can't provide you a different instance per request even though you told it to because you didn't tell it how to create a new instance, you instead provided a specific instance.
If you need a different instance of an object per lifetime scope/request/whatever, you need to register a type, a delegate, or something else that tells Autofac how to create that new instance.
What that means is that if you want a different IUserNameFromFrontEnd per request, you need to move that logic out of a DelegatingHandler and into an Autofac registration delegate.
// Make sure to register the HttpRequestMessage in the container
// so you can resolve it...
builder.RegisterHttpRequestMessage(httpConfiguration);
// Then, whilst building your root container...
builder
.Register(ctx =>
{
var request = ctx.Resolve<HttpRequestMessage>();
return ExtractUserName(request);
})
.As<IUserNameFromFrontEnd>()
.InstancePerRequest();
Now it will probably do what you're looking to do - because you told Autofac how to create the instance that belongs in each request. It also means you don't need that DelegatingHandler anymore because Autofac will just do the right thing.
More advanced (and probably not useful here, but for completeness):
If, for whatever reason, you still feel like you need to modify the registration directly in the lifetime scope, instead of updating the container you should add the registration when the request lifetime scope is created.
Again, do not update the root container for per-lifetime-scope or per-request dependencies. It's not going to work how you think.
When a new lifetime scope is created, you can add registrations on the fly.
using(var scope = container.BeginLifetimeScope(
builder => builder.RegisterInstance(myfoo).As<IFoo>()))
{
// This will use the registrations in the container
// and the scope. f == myfoo
var f = scope.Resolve<IFoo>();
}
The AutofacDependencyResolver is the thing that creates the request lifetime scope and hands it off to Web API. You can see the full source here. The key method is BeginScope:
public IDependencyScope BeginScope()
{
var lifetimeScope = _container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
return new AutofacWebApiDependencyScope(lifetimeScope);
}
If you create your own AutofacDependencyResolver you can modify how the scope is created:
public IDependencyScope BeginScope()
{
var lifetimeScope = _container.BeginLifetimeScope(
MatchingScopeLifetimeTags.RequestLifetimeScopeTag,
builder => builder.RegisterInstance(myfoo).As<IFoo>());
return new AutofacWebApiDependencyScope(lifetimeScope);
}
This isn't an explicitly supported extension point in the Autofac Web API integration right now - that's why you'd have to create your own resolver.
However, this seems like overkill to solve the thing it appears you're trying to solve. I strongly recommend just registering the delegate with Autofac rather than trying to update existing containers or scopes. You will have far more luck using the path of least resistance.

Resources