Kunal Mukherjee
Kunal Mukherjee

Reputation: 5853

Looping Prompt in BotFramework C# until it validates

I'm trying to inherit the PromptString class and loop the prompt until the user gives a correct input.

I have written the following code to validate if the user specifies a valid date or not.


This is my Custom Prompt class, where I want to validate if user enters a correct date or not, if he enters a correct date, it should simply exit the dialog, else it should re-prompt for the input again.

namespace CustomPromptDemo.Models
{
    public class CustomPrompt : PromptString
    { 
        public CustomPrompt(string Prompt = "Please enter a date", string Retry = "Please enter a valid date!!!", int RetryCount = 3) : base(Prompt, Retry, RetryCount)
            {

            }

        protected override IMessageActivity MakePrompt(IDialogContext context, string prompt, IReadOnlyList<string> options = null, IReadOnlyList<string> descriptions = null, string speak = null)
        {
            return base.MakePrompt(context, prompt, options, descriptions, speak);
        }

        bool ValidateIfDateOrNot(string Query)
        {
            try
            {
                DateTime date = default(DateTime);
                DateTimeModel model = DateTimeRecognizer.GetInstance().GetDateTimeModel(Culture.English);
                List<ModelResult> parsedResults = model.Parse(Query);

                var resolvedValue = (parsedResults.SelectMany(x => x.Resolution).FirstOrDefault().Value as List<Dictionary<string, string>>).SelectMany(x => x).Where(x => x.Key.Equals("value")).Select(y => y.Value).FirstOrDefault();

                return DateTime.TryParse(resolvedValue, out date);
            }
            catch (Exception ex)
            {
                throw;
            }
        }
        protected override async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> message)
        {
            IMessageActivity msg = await message;

            if (ValidateIfDateOrNot(msg.Text))
            {
                await context.PostAsync("Valid date!");
                context.Done<object>(null);
            }
            else
            {
                await context.PostAsync("Please enter a valid date!");
                await base.MessageReceivedAsync(context, message);
            }
        }
        protected override bool TryParse(IMessageActivity message, out string result)
        {
            return base.TryParse(message, out result);
        }
    }
}

This is my RootDialog

    namespace CustomPromptDemo.Dialogs
    {
        [Serializable]
        public class RootDialog : IDialog<object>
        {
            public Task StartAsync(IDialogContext context)
            {
                context.Wait(MessageReceivedAsync);

                return Task.CompletedTask;
            }

            private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
            {
                CustomPrompt c = new CustomPrompt();
                c.
            }
        }
    }

I am having a multitude of methods to choose from like in the below screenshot.

enter image description here

I am having trouble invoking the custom prompt I made. Any help is appreciated, thanks.

Upvotes: 0

Views: 1098

Answers (3)

Thulani Chivandikwa
Thulani Chivandikwa

Reputation: 3539

You can make use of the DateTimePrompt or if you really want more control a simple TextPrompt and provide a custom validator for it:

AddDialog(new DateTimePrompt(nameof(DateTimePrompt), DateTimePromptValidator));

Within your dialog you could kick off the prompt providing a retry prompt for a separate message to be shown when the retry fails:

return await stepcontext.PromptAsync(nameof(DateTimePrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Please enter the date."),
                RetryPrompt = MessageFactory.Text("Please enter a valid date")

            }, cancellationtoken);

If you want to handle advanced cases with your dates like multiple languages I suggest taking a look at Recognizers-Text from Microsoft which currently power pre-build entities in LUIS: Language Understanding Intelligent Service.

Upvotes: 1

James Mundy
James Mundy

Reputation: 4329

I have built something very similar in my bot that prompts the user for an image and then continues looping if they don't input a valid image:

[Serializable]
public class PhotoInputDialog : IDialog<string>
{
    public ICustomInputValidator<IMessageActivity> Validator { get; private set; }

    public string InputPrompt { get; private set; }
    public string WrongInputPrompt { get; private set; }

    public static PhotoInputDialog Create
        (string inputPrompt, string wrongInputPrompt, ICustomInputValidator<IMessageActivity> validator)
    {
        return new PhotoInputDialog()
        { InputPrompt = inputPrompt, WrongInputPrompt = wrongInputPrompt, Validator = validator };
    }

    public async Task StartAsync(IDialogContext context)
    {
        await context.PostAsync(InputPrompt);
        context.Wait(InputGiven);
    }

    private async Task InputGiven(IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
        if (!Validator.IsValid(message))
        {
            await context.PostAsync(WrongInputPrompt);
            context.Wait(InputGiven);
        }
        else
            context.Done(message.Attachments.First().ContentUrl);
    }
}

How it works:

  1. Prompt user for input
  2. Wait for photo
  3. Verify input
  4. If valid, then call context.Done or if it fails show a message to the user and await an input again.

When I create the dialog I submit a validator class that I use to validate the result, here's one I created to verify text input on a certain length. It's not 100% necessary but it is nice and reusable now:

[Serializable()]
public class TextCustomInputValidator : ICustomInputValidator<string>
{
    private int MinLength, MaxLength;
    public TextCustomInputValidator(int minLength, int maxLength)
    {
        MinLength = minLength;
        MaxLength = maxLength;
    }
    public bool IsValid(string input)
    {
        return input.Length >= MinLength && input.Length <= MaxLength;
    }
}

There are a multitude of different ways to do a Bot in the Bot Framework so there are probably other ways it is possible. Another that springs to mind is using FormFlow as that has a date input validator: https://docs.botframework.com/en-us/csharp/builder/sdkreference/dc/db8/class_microsoft_1_1_bot_1_1_builder_1_1_form_flow_1_1_advanced_1_1_recognize_date_time.html

Upvotes: 1

Nicolas R
Nicolas R

Reputation: 14609

You can use it just like PromptDialog.Text is using it, see on GitHub sources:

public class PromptDialog
{
    public static void Text(IDialogContext context, ResumeAfter<string> resume, string prompt, string retry = null, int attempts = 3)
    {
        var child = new PromptString(prompt, retry, attempts);
        context.Call<string>(child, resume);
    }

So in your case:

[Serializable]
public class RootDialog : IDialog<object>
{
    public Task StartAsync(IDialogContext context)
    {
        context.Wait(MessageReceivedAsync);

        return Task.CompletedTask;
    }

    private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
    {
        var prompt = new CustomPrompt();
        context.Call<string>(prompt, ResumeAfterPromptString);
    }

    private async Task ResumeAfterPromptString(IDialogContext context, IAwaitable<string> result)
    {
        // Do what you want here... But your customPrompt should return a string

        context.Done<object>(null);
    }
}

By the way, you also have to make your CustomPrompt serializable:

[Serializable]
public class CustomPrompt : PromptDialog.PromptString

And you should return your string instead of null if ValidateIfDateOrNot is ok.

EDIT: last thing, you should have a look to the sample provided on GitHub's project about the recognizer you are using, here , which is a good example of the parsing of a date just like you want

Upvotes: 1

Related Questions