/// <summary> /// Runs dialog system in the context of an ITurnContext. /// </summary> /// <param name="context">turn context.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>result of the running the logic against the activity.</returns> public async Task <DialogManagerResult> OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default) { var botStateSet = new BotStateSet(); // Preload TurnState with DM TurnState. foreach (var pair in InitialTurnState) { context.TurnState.Set(pair.Key, pair.Value); } // register DialogManager with TurnState. context.TurnState.Set(this); if (ConversationState == null) { ConversationState = context.TurnState.Get <ConversationState>() ?? throw new InvalidOperationException($"Unable to get an instance of {nameof(ConversationState)} from turnContext."); } else { context.TurnState.Set(ConversationState); } botStateSet.Add(ConversationState); if (UserState == null) { UserState = context.TurnState.Get <UserState>(); } else { context.TurnState.Set(UserState); } if (UserState != null) { botStateSet.Add(UserState); } // create property accessors var lastAccessProperty = ConversationState.CreateProperty <DateTime>(LastAccess); var lastAccess = await lastAccessProperty.GetAsync(context, () => DateTime.UtcNow, cancellationToken).ConfigureAwait(false); // Check for expired conversation if (ExpireAfter.HasValue && (DateTime.UtcNow - lastAccess) >= TimeSpan.FromMilliseconds((double)ExpireAfter)) { // Clear conversation state await ConversationState.ClearStateAsync(context, cancellationToken).ConfigureAwait(false); } lastAccess = DateTime.UtcNow; await lastAccessProperty.SetAsync(context, lastAccess, cancellationToken).ConfigureAwait(false); // get dialog stack var dialogsProperty = ConversationState.CreateProperty <DialogState>(_dialogStateProperty); var dialogState = await dialogsProperty.GetAsync(context, () => new DialogState(), cancellationToken).ConfigureAwait(false); // Create DialogContext var dc = new DialogContext(Dialogs, context, dialogState); // Call the common dialog "continue/begin" execution pattern shared with the classic RunAsync extension method var turnResult = await DialogExtensions.InternalRunAsync(context, _rootDialogId, dc, StateConfiguration, cancellationToken).ConfigureAwait(false); // save BotState changes await botStateSet.SaveAllChangesAsync(dc.Context, false, cancellationToken).ConfigureAwait(false); return(new DialogManagerResult { TurnResult = turnResult }); }
/// <summary> /// Runs dialog system in the context of an ITurnContext. /// </summary> /// <param name="context">turn context.</param> /// <param name="cancellationToken">cancelation token.</param> /// <returns>result of the running the logic against the activity.</returns> public async Task <DialogManagerResult> OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default(CancellationToken)) { var botStateSet = new BotStateSet(); // preload turnstate with DM turnstate foreach (var pair in this.TurnState) { context.TurnState.Set(pair.Key, pair.Value); } if (this.ConversationState == null) { this.ConversationState = context.TurnState.Get <ConversationState>() ?? throw new ArgumentNullException(nameof(this.ConversationState)); } else { context.TurnState.Set(this.ConversationState); } botStateSet.Add(this.ConversationState); if (this.UserState == null) { this.UserState = context.TurnState.Get <UserState>(); } if (this.UserState != null) { botStateSet.Add(this.UserState); } // create property accessors var lastAccessProperty = ConversationState.CreateProperty <DateTime>(LASTACCESS); var lastAccess = await lastAccessProperty.GetAsync(context, () => DateTime.UtcNow, cancellationToken : cancellationToken).ConfigureAwait(false); // Check for expired conversation var now = DateTime.UtcNow; if (this.ExpireAfter.HasValue && (DateTime.UtcNow - lastAccess) >= TimeSpan.FromMilliseconds((double)this.ExpireAfter)) { // Clear conversation state await ConversationState.ClearStateAsync(context, cancellationToken : cancellationToken).ConfigureAwait(false); } lastAccess = DateTime.UtcNow; await lastAccessProperty.SetAsync(context, lastAccess, cancellationToken : cancellationToken).ConfigureAwait(false); // get dialog stack var dialogsProperty = ConversationState.CreateProperty <DialogState>(this.dialogStateProperty); DialogState dialogState = await dialogsProperty.GetAsync(context, () => new DialogState(), cancellationToken : cancellationToken).ConfigureAwait(false); // Create DialogContext var dc = new DialogContext(this.Dialogs, context, dialogState); // get the dialogstatemanager configuration var dialogStateManager = new DialogStateManager(dc); await dialogStateManager.LoadAllScopesAsync(cancellationToken).ConfigureAwait(false); dc.Context.TurnState.Add(dialogStateManager); DialogTurnResult turnResult = null; // Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn. // // NOTE: We loop around this block because each pass through we either complete the turn and break out of the loop // or we have had an exception AND there was an OnError action which captured the error. We need to continue the // turn based on the actions the OnError handler introduced. while (true) { try { if (dc.ActiveDialog == null) { // start root dialog turnResult = await dc.BeginDialogAsync(this.rootDialogId, cancellationToken : cancellationToken).ConfigureAwait(false); } else { // Continue execution // - This will apply any queued up interruptions and execute the current/next step(s). turnResult = await dc.ContinueDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false); if (turnResult.Status == DialogTurnStatus.Empty) { // restart root dialog turnResult = await dc.BeginDialogAsync(this.rootDialogId, cancellationToken : cancellationToken).ConfigureAwait(false); } } // turn successfully completed, break the loop break; } catch (Exception err) { // fire error event, bubbling from the leaf. var handled = await dc.EmitEventAsync(DialogEvents.Error, err, bubble : true, fromLeaf : true, cancellationToken : cancellationToken).ConfigureAwait(false); if (!handled) { // error was NOT handled, throw the exception and end the turn. (This will trigger the Adapter.OnError handler and end the entire dialog stack) throw; } } } // save all state scopes to their respective botState locations. await dc.GetState().SaveAllChangesAsync(cancellationToken).ConfigureAwait(false); // save botstate changes await botStateSet.SaveAllChangesAsync(dc.Context, false, cancellationToken).ConfigureAwait(false); // send trace of memory var snapshot = dc.GetState().GetMemorySnapshot(); var traceActivity = (Activity)Activity.CreateTraceActivity("BotState", "https://www.botframework.com/schemas/botState", snapshot, "Bot State"); await dc.Context.SendActivityAsync(traceActivity).ConfigureAwait(false); return(new DialogManagerResult() { TurnResult = turnResult }); }
/// <summary> /// Runs dialog system in the context of an ITurnContext. /// </summary> /// <param name="context">turn context.</param> /// <param name="cancellationToken">cancelation token.</param> /// <returns>result of the running the logic against the activity.</returns> public async Task <DialogManagerResult> OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default(CancellationToken)) { BotStateSet botStateSet = new BotStateSet(); ConversationState conversationState = this.ConversationState ?? context.TurnState.Get <ConversationState>() ?? throw new ArgumentNullException($"{nameof(ConversationState)} is not found in the turn context. Have you called adapter.UseState() with a configured ConversationState object?"); UserState userState = this.UserState ?? context.TurnState.Get <UserState>(); if (conversationState != null) { botStateSet.Add(conversationState); } if (userState != null) { botStateSet.Add(userState); } // create property accessors var lastAccessProperty = conversationState.CreateProperty <DateTime>(LASTACCESS); var lastAccess = await lastAccessProperty.GetAsync(context, () => DateTime.UtcNow, cancellationToken : cancellationToken).ConfigureAwait(false); // Check for expired conversation var now = DateTime.UtcNow; if (this.ExpireAfter.HasValue && (DateTime.UtcNow - lastAccess) >= TimeSpan.FromMilliseconds((double)this.ExpireAfter)) { // Clear conversation state await conversationState.ClearStateAsync(context, cancellationToken : cancellationToken).ConfigureAwait(false); } lastAccess = DateTime.UtcNow; await lastAccessProperty.SetAsync(context, lastAccess, cancellationToken : cancellationToken).ConfigureAwait(false); // get dialog stack var dialogsProperty = conversationState.CreateProperty <DialogState>(DIALOGS); DialogState dialogState = await dialogsProperty.GetAsync(context, () => new DialogState(), cancellationToken : cancellationToken).ConfigureAwait(false); // Create DialogContext var dc = new DialogContext(this.dialogSet, context, dialogState); // set DSM configuration dc.SetStateConfiguration(this.StateConfiguration ?? DialogStateManager.CreateStandardConfiguration(conversationState, userState)); // load scopes await dc.GetState().LoadAllScopesAsync(cancellationToken).ConfigureAwait(false); DialogTurnResult turnResult = null; if (dc.ActiveDialog == null) { // start root dialog turnResult = await dc.BeginDialogAsync(this.rootDialogId, cancellationToken : cancellationToken).ConfigureAwait(false); } else { // Continue execution // - This will apply any queued up interruptions and execute the current/next step(s). turnResult = await dc.ContinueDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false); if (turnResult.Status == DialogTurnStatus.Empty) { // restart root dialog turnResult = await dc.BeginDialogAsync(this.rootDialogId, cancellationToken : cancellationToken).ConfigureAwait(false); } } // save all state scopes to their respective stores. await dc.GetState().SaveAllChangesAsync(cancellationToken).ConfigureAwait(false); // save botstate changes await botStateSet.SaveAllChangesAsync(dc.Context, false, cancellationToken).ConfigureAwait(false); // send trace of memory var snapshot = dc.GetState().GetMemorySnapshot(); var traceActivity = (Activity)Activity.CreateTraceActivity("BotState", "https://www.botframework.com/schemas/botState", snapshot, "Bot State"); await dc.Context.SendActivityAsync(traceActivity).ConfigureAwait(false); return(new DialogManagerResult() { TurnResult = turnResult }); }
/// <summary> /// Runs dialog system in the context of an ITurnContext. /// </summary> /// <param name="context">turn context.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>result of the running the logic against the activity.</returns> public async Task <DialogManagerResult> OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default) { var botStateSet = new BotStateSet(); // Preload TurnState with DM TurnState. foreach (var pair in InitialTurnState) { context.TurnState.Set(pair.Key, pair.Value); } // register DialogManager with TurnState. context.TurnState.Set(this); if (ConversationState == null) { ConversationState = context.TurnState.Get <ConversationState>() ?? throw new InvalidOperationException($"Unable to get an instance of {nameof(ConversationState)} from turnContext."); } else { context.TurnState.Set(ConversationState); } botStateSet.Add(ConversationState); if (UserState == null) { UserState = context.TurnState.Get <UserState>(); } else { context.TurnState.Set(UserState); } if (UserState != null) { botStateSet.Add(UserState); } // create property accessors var lastAccessProperty = ConversationState.CreateProperty <DateTime>(LastAccess); var lastAccess = await lastAccessProperty.GetAsync(context, () => DateTime.UtcNow, cancellationToken).ConfigureAwait(false); // Check for expired conversation if (ExpireAfter.HasValue && (DateTime.UtcNow - lastAccess) >= TimeSpan.FromMilliseconds((double)ExpireAfter)) { // Clear conversation state await ConversationState.ClearStateAsync(context, cancellationToken).ConfigureAwait(false); } lastAccess = DateTime.UtcNow; await lastAccessProperty.SetAsync(context, lastAccess, cancellationToken).ConfigureAwait(false); // get dialog stack var dialogsProperty = ConversationState.CreateProperty <DialogState>(_dialogStateProperty); var dialogState = await dialogsProperty.GetAsync(context, () => new DialogState(), cancellationToken).ConfigureAwait(false); // Create DialogContext var dc = new DialogContext(Dialogs, context, dialogState); // promote initial TurnState into dc.services for contextual services foreach (var service in dc.Services) { dc.Services[service.Key] = service.Value; } // map TurnState into root dialog context.services foreach (var service in context.TurnState) { dc.Services[service.Key] = service.Value; } // get the DialogStateManager configuration var dialogStateManager = new DialogStateManager(dc, StateConfiguration); await dialogStateManager.LoadAllScopesAsync(cancellationToken).ConfigureAwait(false); dc.Context.TurnState.Add(dialogStateManager); DialogTurnResult turnResult = null; // Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn. // // NOTE: We loop around this block because each pass through we either complete the turn and break out of the loop // or we have had an exception AND there was an OnError action which captured the error. We need to continue the // turn based on the actions the OnError handler introduced. var endOfTurn = false; while (!endOfTurn) { try { if (context.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims)) { // The bot is running as a skill. turnResult = await HandleSkillOnTurnAsync(dc, cancellationToken).ConfigureAwait(false); } else { // The bot is running as root bot. turnResult = await HandleBotOnTurnAsync(dc, cancellationToken).ConfigureAwait(false); } // turn successfully completed, break the loop endOfTurn = true; }
public DefaultTestAdapter(BotStateSet botStateSet) : base(sendTraceActivity: false) { Use(new EventDebuggerMiddleware()); Use(new AutoSaveStateMiddleware(botStateSet)); }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { try { services.AddBot <AADv2Bot>(options => { options.CredentialProvider = new ConfigurationCredentialProvider(Configuration); // The CatchExceptionMiddleware provides a top-level exception handler for your bot. // Any exceptions thrown by other Middleware, or by your OnTurn method, will be // caught here. To facillitate debugging, the exception is sent out, via Trace, // to the emulator. Trace activities are NOT displayed to users, so in addition // an "Ooops" message is sent. // The Memory Storage used here is for local bot debugging only. When the bot // is restarted, anything stored in memory will be gone. IStorage dataStore = new MemoryStorage(); var convoState = new ConversationState(dataStore); var userState = new UserState(dataStore); // The File data store, shown here, is suitable for bots that run on // a single machine and need durable state across application restarts. // IStorage dataStore = new FileStorage(System.IO.Path.GetTempPath()); // For production bots use the Azure Table Store, Azure Blob, or // Azure CosmosDB storage provides, as seen below. To include any of // the Azure based storage providers, add the Microsoft.Bot.Builder.Azure // Nuget package to your solution. That package is found at: // https://www.nuget.org/packages/Microsoft.Bot.Builder.Azure/ // IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureTableStorage("AzureTablesConnectionString", "TableName"); // IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureBlobStorage("AzureBlobConnectionString", "containerName"); options.State.Add(convoState); options.State.Add(userState); // Add State to BotStateSet Middleware (that require auto-save) // The BotStateSet Middleware forces state storage to auto-save when the Bot is complete processing the message. // Note: Developers may choose not to add all the State providers to this Middleware if save is not required. var stateSet = new BotStateSet(options.State.ToArray()); options.Middleware.Add(stateSet); }); services.AddSingleton <AADv2BotAccessors>(sp => { var options = sp.GetRequiredService <IOptions <BotFrameworkOptions> >().Value; if (options == null) { throw new InvalidOperationException("BotFrameworkOptions must be configured prior to setting up the State Accessors"); } var conversationState = options.State.OfType <ConversationState>().FirstOrDefault(); var userState = options.State.OfType <UserState>().FirstOrDefault(); if (conversationState == null) { throw new InvalidOperationException("ConversationState must be defined and added before adding conversation-scoped state accessors."); } // Create Custom State Property Accessors // State Property Accessors enable components to read and write individual properties, without having to // pass the entire State object. var accessors = new AADv2BotAccessors { CommandState = userState.CreateProperty <string>(AADv2BotAccessors.CommandStateName), ConversationDialogState = conversationState.CreateProperty <DialogState>(AADv2BotAccessors.DialogStateName), }; return(accessors); }); } catch (Exception e) { Console.WriteLine(e); throw; } }
public async Task BotStateSet_Chain() { var storage = new MemoryStorage(); // setup userstate var userState = new UserState(storage); var userProperty = userState.CreateProperty <int>("userCount"); // setup convState var convState = new ConversationState(storage); var convProperty = convState.CreateProperty <int>("convCount"); var bss = new BotStateSet() .Use(userState) .Use(convState); var adapter = new TestAdapter() .Use(bss); const int USER_INITITAL_COUNT = 100; const int CONVERSATION_INITIAL_COUNT = 10; BotCallbackHandler botLogic = async(context, cancellationToken) => { // get userCount and convCount from botStateSet var userCount = await userProperty.GetAsync(context, () => USER_INITITAL_COUNT).ConfigureAwait(false); var convCount = await convProperty.GetAsync(context, () => CONVERSATION_INITIAL_COUNT).ConfigureAwait(false); if (context.Activity.Type == ActivityTypes.Message) { if (context.Activity.Text == "get userCount") { await context.SendActivityAsync(context.Activity.CreateReply($"{userCount}")); } else if (context.Activity.Text == "get convCount") { await context.SendActivityAsync(context.Activity.CreateReply($"{convCount}")); } } // increment userCount and set property using accessor. To be saved later by BotStateSet userCount++; await userProperty.SetAsync(context, userCount); // increment convCount and set property using accessor. To be saved later by BotStateSet convCount++; await convProperty.SetAsync(context, convCount); }; await new TestFlow(adapter, botLogic) .Send("test1") .Send("get userCount") .AssertReply((USER_INITITAL_COUNT + 1).ToString()) .Send("get userCount") .AssertReply((USER_INITITAL_COUNT + 2).ToString()) .Send("get convCount") .AssertReply((CONVERSATION_INITIAL_COUNT + 3).ToString()) .StartTestAsync(); // new adapter on new conversation var bss2 = new BotStateSet() .Use(userState) .Use(convState); adapter = new TestAdapter(new ConversationReference { ChannelId = "test", ServiceUrl = "https://test.com", User = new ChannelAccount("user1", "User1"), Bot = new ChannelAccount("bot", "Bot"), Conversation = new ConversationAccount(false, "convo2", "Conversation2") }) .Use(bss2); await new TestFlow(adapter, botLogic) .Send("get userCount") .AssertReply((USER_INITITAL_COUNT + 4).ToString(), "user count should continue on new conversation") .Send("get convCount") .AssertReply((CONVERSATION_INITIAL_COUNT + 1).ToString(), "conversationCount for conversation2 should be reset") .StartTestAsync(); }