Code Examples

Examples

A Root dialog, Factory and a SubDialogLauncher coordinating flow.

[Serializable]

public class MyRootDialog : AturaCoordinatorDialogBase

{

    public override ResourceManager ClientSpecificStrings =\> NgiBotStrings.ResourceManager;

    public override string StartDialogMessage =\> NgiBotStrings.Welcome;

    public override string ExtLanguageApiAppId =\> ConfigurationManager.AppSettings["LuisAppId"];

    public override string ExtLanguageApiAppSecret =\> Environment.GetEnvironmentVariable("LuisAppSecret");

    public override AturaDialogLauncherBase DialogLauncher { get; set; } = new NgiDialogLauncher();

    public override async Task PostStartDialogMessageAsync(AturaDialogContext context, IDialogContext dc)

    {

        await base.PostStartDialogMessageAsync(context, dc);

        var mainMenuDialog = MyMainMenuDialog.CreateWithDefaultButtons();

        context.LaunchSubDialog(dc, mainMenuDialog, ResumeRootDialog);

    }

    protected override Task HandleDontUnderstandResponse(AturaDialogContext context, IDialogContext dc, IMessageActivity message, LanguageResult interpreted)

    {

        //Let this root dialog decide what to do with the user's dontUnderstand result.

        context.LaunchSubDialog(dc, new MyDontUnderstandDialog(), ResumeRootDialog);

        return Task.CompletedTask;

    }

    protected override async Task\<bool\> HandleLogoutAsync(AturaDialogContext context, IDialogContext dc, LanguageResult interpreted)

    {

        //Logout can be requested anywhere in code. This could also go into a client-specific base class.

        context.ClearSavedSecurityInfo(dc.UserData);

        await context.PostToUserAsync(dc, NgiBotStrings.Thanks\_You\_Have\_Been\_Logged\_Out);

        var mainMenuDialog = MyMainMenuDialog.CreateWithDefaultButtons();

        context.LaunchSubDialog(dc, mainMenuDialog, ResumeRootDialog);

        //Return true to indicate this dialog did the logout.

        return true;

    }

}

public class MyRootDialogFactory: IRootDialogFactory

{

    public AturaDialogBase CreateRootDialog(Activity initiatingActivity)

    {

        //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".

        return new MyRootDialog();

    }

}

[Serializable]

public class MyDialogLauncher: 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\>

public override List\<GetLaunchInstructions\> SubDialogLaunchPredicates { get; set; } =

    new List\<GetLaunchInstructions\>

    {

        //AskAboutProducts

        (context, dc, interpreted, msg) =\>

        {

            if(MatchesActiveIntent(interpreted.CertainIntent?.IntentString, Intents.Finance.AskAboutProducts))

            {

                return !context.HasUserRoleBeenSet()

                    ? new SubDialogLaunchInstructions(true, LaunchOrForwardTo.Forward, (lrr) =\> new AskForUserRoleDialog(false))

                    : new SubDialogLaunchInstructions(

                        true,

                        LaunchOrForwardTo.Forward,

                        (lrr) =\> new ProductInfoDialog(false, interpreted));

            }

            return null;

        },

        //DocumentSearch

        (context, dc, interpreted, msg) =\>

        {

            return MatchesActiveIntent(interpreted.CertainIntent?.IntentString, Intents.DocumentSearch)

                ? new SubDialogLaunchInstructions(

                    true,

                    LaunchOrForwardTo.Forward,

                    (langResult) =\> new DocumentSearchDialog(langResult))

                : null;

        }

    };

}

A dialog that repeats whatever the user says.

[Serializable]

public class EchoDialog : AturaDialogBase

{

    public override string StartDialogMessage { get; } = "Welcome to the bot";

    //The "SilentInterpreter" does not call any NLP engine. Just passes back a

    //  raw languageResult containing the user text.

    protected override AturaInterpreter Interpreter { get; set; }

        = new SilentInterpreter();

    public override async Task ProcessAturaMessageAsync(

        AturaDialogContext context,

        IDialogContext dc,

        IMessageActivity message)

    {

        //Important to call here, initialized logged-in token.

        await base.ProcessAturaMessageAsync(context, dc, message);

        var langresult = await new SilentInterpreter()

        .InterpretAndSaveMessageAsync(

            context,

            dc,

            message,

            ExtLanguageApiAppId,

            ExtLanguageApiAppSecret);

        //Echo what the user said.

        await context.PostToUserAsync(dc, "You said: " + message.Text);

        //Wait here for the user's next message.

        context.WaitThenContinueAt(dc, ProcessAturaMessageAsync);

    }

    public override string ExtLanguageApiAppId =\> null;

    public override string ExtLanguageApiAppSecret =\> null;

    public override ResourceManager ClientSpecificStrings =\>

                                        Resources.ResourceManager;



    protected override Task\<bool\> HandleLogoutAsync(

        AturaDialogContext context,

        IDialogContext dc,

        LanguageResult interpreted)

    {

        //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)

        throw new NotImplementedException();

    }

}

A dialog that pauses the bot and initiates agent takeover.

[Serializable]

public class MyLiveAgentHelpDialog: MyBaseDialogWithReusableLogic

{

    public override async Task PostStartDialogMessageAsync(AturaDialogContext context, IDialogContext dc)

    {

        await base.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.)

        await SetUserNeedsAssistanceAsync(dc);

        CurrentContext.FinishDialog(dc);

    }

    public override string StartDialogMessage { get; } = null;

    public override string ExtLanguageApiAppId { get; } = null;

    public override string ExtLanguageApiAppSecret { get; } = null;

    public override ResourceManager ClientSpecificStrings { get; } = MyBotStrings.ResourceManager;

}

1.
  1. 1.4A dialog that shows multiple-choice buttons (mandatory and non-mandatory).

//Mandatory Yes/No choice.

private async Task ConfirmExistingClientOrEnterNewName(AturaDialogContext context, IDialogContext dc)

{

    await context.PromptMandatoryUserInputChoicesAsync(

        dc: dc,

        prompt: string.Format(MyBotStrings.ClientNamePrompt, CurrentUser),

        options: new string[] { MyBotStrings.Common\_Yes, MyBotStrings.Common\_No},

        resultCallback: ResumeAfterYesNoSelection);

}

//Non-mandatory choices. Bot also renders a "Cancel" button.

private async Task AskUserToChooseBankAccount(AturaDialogContext context, IDialogContext dc)

{

    string[] bankAccountNumbers = Api.GetBankAccountNumbers(CurrentUser);

    await context.PromptUserInputChoicesAsync(

        dc: dc,

        prompt: string.Format(MyBotStrings.BankAccountSelectionPrompt, CurrentUser),

        options: bankAccountNumbers,

        resultCallback: ResumeAfterBankAccountSelection,

        nullResultCallback: ResumeAfterUserCancelled);

}

private Task ResumeAfterBankAccountSelection(IDialogContext context, string result)

{

    //Chosen bank account number will be in "result" parameter.

    // ....

}

private Task ResumeAfterUserCancelled(IDialogContext context)

{

    //Custom logic if user changed their mind, and did not choose a bank account.

    // ....

}

A unit test that tests a flow.

[TestClass]

public class TestSearchForDocument : AturaDialogTestBase

{

    [TestMethod]

    public async Task AskAbout\_Debt()

    {

        var expectedData = GetFakeDebitDocumentData();

        await TestFlowAsync(

            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.

private async Task AskForUserEmailAddress(IDialogContext dc)

{

    //Use PromptGetEmailDialog to get a valid email address.

    //  The dialog does all the necessary validation.

    var emailPromptDialog = new PromptGetEmailDialog(

        MyBotStrings.ReportingStatement\_WhichAddress,

        MyBotStrings.Reporting\_EmailValidation);

    CurrentContext.LaunchSubDialog(dc,

        emailPromptDialog,

        GotUserEmailAddress);

}

A dialog that initiates a very slow external web service call.

private Task StartBankAccountsApiCallAsync(AturaDialogContext context, IDialogContext dc)

{

    if (dc.UserData.TryGetValue(MyStateKeys.BankAccountNr, out int bankAccountNr))

    {

        //We already have a chosen bank account. Carry on.

        await GotBankAccountNr(context, dc, bankAccountNr);

        return;

    }

    var slowCallDialog = new SlowCallPurgatoryDialog(

        param: context,

        slowCall: DoSlowBankAccountNrCall);

    context.LaunchSubDialog(dc, slowCallDialog, GotBankAccountNr);

}

private async Task\<string\> DoSlowBankAccountNrCall(IDialogContext dc, object contextParam)

{

    //Add try/catch and error handling in real code!

    using(var containerScope = AturaDialogContext.DiContainer.BeginLifetimeScope())

    {

        var bankDataApi = containerScope.Resolve\<IBankDataApi\>();

        var bankDataResponse = await bankDataApi.BankAccountsRequest(CurrentUser.IdNr);

        return JsonConvert.SerializeObject(bankDataResponse);

    }

}

private async Task GotBankAccountNr(AturaDialogContext context, IDialogContext dc, AturaDialogResult slowCallResult)

{

    if (slowCallResult.Cancelled)

    {

        //Go back to main menu.

        context.FinishDialog(dc);

    }

    else if (slowCallResult.Crashed)

    {

        var liveAgentDialog = new MyLiveAgentTakeoverDialog();

        context.LaunchSubDialog(dc, liveAgentDialog, ResumeAfterSubDialogAndFinishThisDialogAsync);

    }

    else

    {

        var apiResult = JsonConvert.DeserializeObject\<BankAccountResult\>(slowCallResult);

        /* Do what we need to with the slow call result */

    }

}