A Root dialog, Factory and a SubDialogLauncher coordinating flow.
[Serializable]publicclassMyRootDialog:AturaCoordinatorDialogBase{publicoverrideResourceManagerClientSpecificStrings=\>NgiBotStrings.ResourceManager;publicoverridestringStartDialogMessage=\>NgiBotStrings.Welcome;publicoverridestringExtLanguageApiAppId=\>ConfigurationManager.AppSettings["LuisAppId"];publicoverridestringExtLanguageApiAppSecret=\>Environment.GetEnvironmentVariable("LuisAppSecret");publicoverrideAturaDialogLauncherBaseDialogLauncher{get;set;}=newNgiDialogLauncher();publicoverrideasyncTaskPostStartDialogMessageAsync(AturaDialogContextcontext,IDialogContextdc){awaitbase.PostStartDialogMessageAsync(context,dc);varmainMenuDialog=MyMainMenuDialog.CreateWithDefaultButtons();context.LaunchSubDialog(dc,mainMenuDialog,ResumeRootDialog);}protectedoverrideTaskHandleDontUnderstandResponse(AturaDialogContextcontext,IDialogContextdc,IMessageActivitymessage,LanguageResultinterpreted){//Let this root dialog decide what to do with the user's dontUnderstand result.context.LaunchSubDialog(dc,newMyDontUnderstandDialog(),ResumeRootDialog);returnTask.CompletedTask;}protectedoverrideasyncTask\<bool\>HandleLogoutAsync(AturaDialogContextcontext,IDialogContextdc,LanguageResultinterpreted){//Logout can be requested anywhere in code. This could also go into a client-specific base class.context.ClearSavedSecurityInfo(dc.UserData);awaitcontext.PostToUserAsync(dc,NgiBotStrings.Thanks\_You\_Have\_Been\_Logged\_Out);varmainMenuDialog=MyMainMenuDialog.CreateWithDefaultButtons();context.LaunchSubDialog(dc,mainMenuDialog,ResumeRootDialog);//Return true to indicate this dialog did the logout.returntrue;}}publicclassMyRootDialogFactory:IRootDialogFactory{publicAturaDialogBaseCreateRootDialog(ActivityinitiatingActivity){//Nothing special here, just return standard root dialog.// If we wanted to we could launch a different root dialog (Eg. launching different root dialog// based on the bot "channel".returnnewMyRootDialog();}}
[Serializable]publicclassMyDialogLauncher:AturaDialogLauncherBase{/// \<inheritdoc /\>/// \<summary\>/// A list of functions that take a language result and return a dialog that handles that language result./// Processed in order, and the first Func that returns a valid SubDialogLaunchInstruction is used./// \</summary\>publicoverrideList\<GetLaunchInstructions\>SubDialogLaunchPredicates{get;set;}=newList\<GetLaunchInstructions\>{//AskAboutProducts(context,dc,interpreted,msg)=\>{if(MatchesActiveIntent(interpreted.CertainIntent?.IntentString,Intents.Finance.AskAboutProducts)){return!context.HasUserRoleBeenSet()?newSubDialogLaunchInstructions(true,LaunchOrForwardTo.Forward,(lrr)=\>newAskForUserRoleDialog(false)):newSubDialogLaunchInstructions(true,LaunchOrForwardTo.Forward,(lrr)=\>newProductInfoDialog(false,interpreted));}returnnull;},//DocumentSearch(context,dc,interpreted,msg)=\>{returnMatchesActiveIntent(interpreted.CertainIntent?.IntentString,Intents.DocumentSearch)?newSubDialogLaunchInstructions(true,LaunchOrForwardTo.Forward,(langResult)=\>newDocumentSearchDialog(langResult)):null;}};}
A dialog that repeats whatever the user says.
[Serializable]publicclassEchoDialog:AturaDialogBase{publicoverridestringStartDialogMessage{get;}="Welcome to the bot";//The "SilentInterpreter" does not call any NLP engine. Just passes back a// raw languageResult containing the user text.protectedoverrideAturaInterpreterInterpreter{get;set;}=newSilentInterpreter();publicoverrideasyncTaskProcessAturaMessageAsync(AturaDialogContextcontext,IDialogContextdc,IMessageActivitymessage){//Important to call here, initialized logged-in token.awaitbase.ProcessAturaMessageAsync(context,dc,message);varlangresult=awaitnewSilentInterpreter().InterpretAndSaveMessageAsync(context,dc,message,ExtLanguageApiAppId,ExtLanguageApiAppSecret);//Echo what the user said.awaitcontext.PostToUserAsync(dc,"You said: "+message.Text);//Wait here for the user's next message.context.WaitThenContinueAt(dc,ProcessAturaMessageAsync);}publicoverridestringExtLanguageApiAppId=\>null;publicoverridestringExtLanguageApiAppSecret=\>null;publicoverrideResourceManagerClientSpecificStrings=\>Resources.ResourceManager;protectedoverrideTask\<bool\>HandleLogoutAsync(AturaDialogContextcontext,IDialogContextdc,LanguageResultinterpreted){//Only needs an implementation when bot supports// log-in/log-out functionality. Logout is a common intent that// can be triggered anywhere (at least when a dialog calls// base.TryHandleCommonIntentsAsync)thrownewNotImplementedException();}}
A dialog that pauses the bot and initiates agent takeover.
[Serializable]publicclassMyLiveAgentHelpDialog:MyBaseDialogWithReusableLogic{publicoverrideasyncTaskPostStartDialogMessageAsync(AturaDialogContextcontext,IDialogContextdc){awaitbase.PostStartDialogMessageAsync(context,dc);//Here we just indicate the user needs assistance and exit the dialog so that// the user can continue talking to the bot until an agent takes over.//This could be much more complicated (Eg. check working hours, ask if user wants to// speak to a real person, etc.)awaitSetUserNeedsAssistanceAsync(dc);CurrentContext.FinishDialog(dc);}publicoverridestringStartDialogMessage{get;}=null;publicoverridestringExtLanguageApiAppId{get;}=null;publicoverridestringExtLanguageApiAppSecret{get;}=null;publicoverrideResourceManagerClientSpecificStrings{get;}=MyBotStrings.ResourceManager;}1.1.1.4Adialogthatshowsmultiple-choicebuttons(mandatoryandnon-mandatory).//Mandatory Yes/No choice.privateasyncTaskConfirmExistingClientOrEnterNewName(AturaDialogContextcontext,IDialogContextdc){awaitcontext.PromptMandatoryUserInputChoicesAsync(dc:dc,prompt:string.Format(MyBotStrings.ClientNamePrompt,CurrentUser),options:newstring[]{MyBotStrings.Common\_Yes,MyBotStrings.Common\_No},resultCallback:ResumeAfterYesNoSelection);}//Non-mandatory choices. Bot also renders a "Cancel" button.privateasyncTaskAskUserToChooseBankAccount(AturaDialogContextcontext,IDialogContextdc){string[]bankAccountNumbers=Api.GetBankAccountNumbers(CurrentUser);awaitcontext.PromptUserInputChoicesAsync(dc:dc,prompt:string.Format(MyBotStrings.BankAccountSelectionPrompt,CurrentUser),options:bankAccountNumbers,resultCallback:ResumeAfterBankAccountSelection,nullResultCallback:ResumeAfterUserCancelled);}privateTaskResumeAfterBankAccountSelection(IDialogContextcontext,stringresult){//Chosen bank account number will be in "result" parameter.// ....}privateTaskResumeAfterUserCancelled(IDialogContextcontext){//Custom logic if user changed their mind, and did not choose a bank account.// ....}
A unit test that tests a flow.
[TestClass]publicclassTestSearchForDocument:AturaDialogTestBase{[TestMethod]publicasyncTaskAskAbout\_Debt(){varexpectedData=GetFakeDebitDocumentData();awaitTestFlowAsync(UserSays("Hi there",Intents.Common.UserGreeting),ExpectBotResponses(MyBotStrings.Root\_Greeting,MyBotStrings.Root\_MenuPrompt),//We mock the LUIS response here, as intent: "Intents.Search.DocumentSearch" and entity "debt assistance".UserSays("I'm looking for the debt assistance document",Intents.Search.DocumentSearch,"debt assistance"),ExpectBotResponseWithAttachment(//"Hero cards" are rich bot responses, eg. an image with buttons.ExpectedHeroCard(expectedData.Synopsis,expectedData.ImageUrl,$"{expectedData.Attachment},{AturaActionType.OpenUrl}",$"{expectedData.EmbedCode},{AturaActionType.OpenUrl}"),expectedData.Title));}}
A dialog that does email validation using a special EmailValidation sub dialog.
privateasyncTaskAskForUserEmailAddress(IDialogContextdc){//Use PromptGetEmailDialog to get a valid email address.// The dialog does all the necessary validation.varemailPromptDialog=newPromptGetEmailDialog(MyBotStrings.ReportingStatement\_WhichAddress,MyBotStrings.Reporting\_EmailValidation);CurrentContext.LaunchSubDialog(dc,emailPromptDialog,GotUserEmailAddress);}
A dialog that initiates a very slow external web service call.
privateTaskStartBankAccountsApiCallAsync(AturaDialogContextcontext,IDialogContextdc){if(dc.UserData.TryGetValue(MyStateKeys.BankAccountNr,outintbankAccountNr)){//We already have a chosen bank account. Carry on.awaitGotBankAccountNr(context,dc,bankAccountNr);return;}varslowCallDialog=newSlowCallPurgatoryDialog(param:context,slowCall:DoSlowBankAccountNrCall);context.LaunchSubDialog(dc,slowCallDialog,GotBankAccountNr);}privateasyncTask\<string\>DoSlowBankAccountNrCall(IDialogContextdc,objectcontextParam){//Add try/catch and error handling in real code!using(varcontainerScope=AturaDialogContext.DiContainer.BeginLifetimeScope()){varbankDataApi=containerScope.Resolve\<IBankDataApi\>();varbankDataResponse=awaitbankDataApi.BankAccountsRequest(CurrentUser.IdNr);returnJsonConvert.SerializeObject(bankDataResponse);}}privateasyncTaskGotBankAccountNr(AturaDialogContextcontext,IDialogContextdc,AturaDialogResultslowCallResult){if(slowCallResult.Cancelled){//Go back to main menu.context.FinishDialog(dc);}elseif(slowCallResult.Crashed){varliveAgentDialog=newMyLiveAgentTakeoverDialog();context.LaunchSubDialog(dc,liveAgentDialog,ResumeAfterSubDialogAndFinishThisDialogAsync);}else{varapiResult=JsonConvert.DeserializeObject\<BankAccountResult\>(slowCallResult);/* Do what we need to with the slow call result */}}