How to stop formflow dialog from re-prompting user when validation fails? - botframework

I have a formflow dialog in which one of it's properties is this...
[Describe("Car Mileage")]
[Prompt("Cool! What's the mileage of the car?")]
[Template(TemplateUsage.NotUnderstood, "Sorry, I didn't understand that mileage value. Can you enter it again please?")]
public string Mileage { get; set; }
Ignore the [Template(TemplateUsage.NotUnderstood,... for now, I'll come back to that.
The dialog is built using the following...
var form = builder
.Field(new FieldReflector<CarValuationDialog>(nameof(ValuationOption))
.SetPrompt(new PromptAttribute($"Hi.<br /><br />Are you looking to get a value for a car you're selling, or car you're buying? {{||}}")))
.Field(new FieldReflector<CarValuationDialog>(nameof(RegistrationNumber))
.SetDefine(RegistrationNumberDefinitionMethod))
.Field(new FieldReflector<CarValuationDialog>(nameof(Mileage))
.SetValidate(async (state, value) =>
{
var result = new ValidateResult { IsValid = true, Value = value };
var regex = new Regex("[0-9,]+");
var match = regex.Match((string)value);
if (match.Success)
{
result.IsValid = true;
}
else
{
result.Feedback = "Sorry, I didn't understand that.";
result.IsValid = false;
}
return await Task.FromResult(result);
}))
.Field(
nameof(PreviousOwnerOption),
active: carValuationDialog => carValuationDialog.ValuationOption == ValuationOptions.LookingToSell)
.Field(
nameof(ServiceHistoryOption),
active: carValuationDialog => carValuationDialog.ValuationOption == ValuationOptions.LookingToSell)
.Confirm(Confirmation)
.OnCompletion(GetValuationAndDisplaySummaryToUser);
return form.Build();
This question is related to
`Feedback` text not shown in bot conversation
I'm experimenting with the validation for Mileage, as I've changed that property from int to string in order to allow freeflow text e.g. "23,456 miles". As a side-effect of changing the data type, when the validation for Mileage fails, I get the following...
Not only does the result.Feedback value now get shown to the user (it did not before, when Mileage was an int), which is fine, but also the original question text is shown.
So my main question is - what can I do so that when validation fails, the original question prompt is not shown to the user?
As a side note, when changing Mileage back to int, and the validation fails (result.IsValid = false), result.Feedback is not shown, but [Template(TemplateUsage.NotUndderstood.... is now shown. So it seems the type of the property matters in relation to what validation messages are shown.

Related

BotFramework: Passing additional values via SuggestedActions

I am currently working on a dialog (BotFramework 3.x), that asks the user a span of two numbers. The user should have the option to say "indifferent" if he does not care or it is open end.
So my approach is to have a variety of suggested actions plus an "indifferent" value. The ActionButton should show and write "indifferent" in the chat window but pass a specific int value to the backend:
if (actions != null)
message.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>(actions)
};
message.AttachmentLayout = AttachmentLayoutTypes.Carousel;
And this is how I build together the actions:
CardActions = new List<CardAction>();
for (int i = fromTo.from ?? MinValue; i <= MaxValue; i++)
{
CardActions.Add(new CardAction()
{
Title = i.ToString(),
Value = complexObject,
Text = i.ToString(),
DisplayText = i.ToString(),
Type = ActionTypes.PostBack
});
}
cardActions.Add(new CardAction()
{
Title = "indifferent",
Value = indifferentValue,
Text = "indifferent",
DisplayText = "indifferent"
Type = ActionTypes.PostBack,
});
I am able to get the value in the backend - that is not the problem. What is a problem though is, that the user is not shown hin answer. I want him to see, that he tapped "5" or "indifferent" in the chat history. With ActionTypes.PostBack this does not work. If I use ActionTypes.ImBack I am not able to use a complex JSON object as value - I simply don't get a response in the backend when tapping the suggestedAction. It only works with ActionTypes.ImBack if I use a plain value. But then the chat history shows the value of the action and not the text or displayText, which would make much more sense.
What am I overseeing here??
If I use ActionTypes.ImBack I am not able to use a complex JSON object as value - I simply don't get a response in the backend when tapping the suggestedAction.
To achieve your requirement: display user selection in chat window, you can specify ActionTypes.ImBack and serialize the specified object to a JSON string, like below.
CardActions.Add(new CardAction()
{
Title = i.ToString(),
//serializes to a JSON string
Value = JsonConvert.SerializeObject(complexObject),
Text = i.ToString(),
DisplayText = i.ToString(),
Type = ActionTypes.ImBack
});
Besides, to present buttons/options that the user can tap to provide input, you can also use rich cards or PromptDialog.Choice.
PromptDialog.Choice(
context: context,
resume: ChoiceReceivedAsync,
options: myoptions,
prompt: "Hi. Please Select an option:",
retry: "Selected option not avilabel . Please try again.",
promptStyle: PromptStyle.Auto,
descriptions: desforchoices
);
Test result:

Using FormFlow Bots Framework Quiz Program

Our bot build does a ‘personality quiz’ for the user. Think Buzzfeed.
I have a variety of attributes I want to increase, just integers, based on the user’s selections on a form, then return an end result.
Using Sandwichbot as a template, this is asking something like (paraphrased):
Do you like to help other people? Yes No
Code is like:
.Confirm(async (state) =>
{
switch (state.HelpYesNo)
{
case true: HelpfulValue++; break;
case false: HurtfulValue++; break;
}
return new PromptAttribute("Thanks, choose OK to continue.");
It works fine, but I hate that I have to make the user ‘Confirm’ by typing OK. It’s an extra step, especially if they have to do it after each question.
I tried writing this with a validate instead, eg validate: async (state, response) =>
Which gives a better user experience, but doesn’t actually run the switch-case. I think the formatting of the switch is in the wrong place for a validate? I'm not sure of the syntax here to get 'validate' to process the case.
What’s the right way to do something like this in FormFlow?
Try something like this. Boolean fields also result in a Yes/No question.
[Serializable]
public class QuizForm
{
public int HelpfulValue;
public int HurtfulValue;
[Prompt("Do you like to help people? {||}")]
public bool HelpPeople;
public static IForm<QuizForm> BuildForm()
{
return new FormBuilder<QuizForm>()
.Message("Let the quiz begin...")
.Field(nameof(HelpPeople), validate: ValidateBool)
// other fields
.Build();
}
private static async Task<ValidateResult> ValidateBool(QuizForm state, object value)
{
var TrueOrFalse = (bool) value;
switch (TrueOrFalse)
{
case true: state.HelpfulValue++; break;
case false: state.HurtfulValue++; break;
}
return new ValidateResult{IsValid = true, Value = value};
}
}

Set ModelState Based on Server Side Query Result in MVC5

I have a query that tests for a valid postcode entry:
using (_ctx)
{
try
{
var pc = _ctx.tblPostcodes.Where(z => z.Postcode == postcodeOutward)
.Select(x => new { postcodeId = x.PostcodeID }).Single();
pcId = pc.postcodeId;
}
catch (Exception)
{
pcId = 0;
Response.Redirect("./");
}
}
I don't like how I've done it. It's clumsy and it doesn't show an error (this is my first MVC project).
I'd rather it return a validation error against the Postcode textbox. I have model annotations for various input mistakes, but have to check the postcode against the database.
Any suggestions on how to set ModelState to get a proper response?
You could try:
if(this.ModelState.ContainsKey("postcodeOutward"))
this.ModelState.Add("postcodeOutward", new ModelState());
ModelState state = this.ModelState["postcodeOutward"];
state.Errors.Add("<error_message>");
state.Value = new ValueProviderResult(postcodeOutward, postcodeOutward == null ? "" : postcodeOutward.ToString(), CultureInfo.CurrentCulture);
You could also try having a custom validation attribute that will perform a check against the database and that should automatically populated the this.ModelState property, but I'm not too sure if accessing the database inside of a validation attribute would be a good/recommened approach to take.

data validation on prompt Input box on Windows Phone

RadInputPrompt.Show("Enter the number", MessageBoxButtons.OK, message, InputMode.Text, textBoxStyle, closedHandler: (arg) =>
{
int okButton = arg.ButtonIndex;
if (okButton == 0)
{
//do some check before submit
if (string.IsNullOrEmpty(arg.Text))
{
MessageBox.Show("Please input the number.");
return; //??
}
//submit
}
else
{
return;
}
});
My question is :
I do some data validation (for example: numeric only, the digit count...) before submit
If the input from user is invaild, I hope the Prompt Input Screen can still remain.
If I use "return" keyword, it'll go back to the main screen.
Or is there any other ways of validation (something like AJAX?) that I can use on this prompt sceen rather than do it on code-behind page?
Thanks a lot!
One technique is to just keep looping and showing the input prompt each time the user clicks OK, but fails to satisfy the input validation. You can see an example of this below with the input text box continuing to repeat if the result is not a valid numeric value.
It's also a good idea to add some kind of feedback to the user indicating that the previous input was not acceptable in the event of an invalid submission. An example of this is below where the title of the input text box is changed after the first invalid submission to include text indicating that the input value must be a valid number.
NOTE: Telerik is saying the ShowAsync method should now be used instead of the Show method since it is being deprecated.
string userInput = string.Empty;
int okButton = 0;
bool firstPass = true;
double numericResult;
while (okButton.Equals(0) && string.IsNullOrWhiteSpace(userInput))
{
string inputBoxTitle = (!firstPass) ? "Enter the number (you must enter a valid number)" : "Enter the number";
InputPromptClosedEventArgs args = await RadInputPrompt.ShowAsync(inputBoxTitle, MessageBoxButtons.OKCancel);
okButton = args.ButtonIndex;
firstPass = false;
if (okButton.Equals(0))
{
if (!string.IsNullOrWhiteSpace(args.Text))
{
bool isNumeric = double.TryParse(args.Text, out numericResult);
if (isNumeric)
{
// We have good data, so assign it so we can get out of this loop
userInput = args.Text;
}
}
}
}

Trying to use [Description] data annotation attribute with existing code

SLIGHT UPDATE BELOW
I am trying to use the [Description] data annotation attribute with enums in order to display a friendly name. I've searched around a lot and cannot get anything implemented. Right now I have code that will display an enum as a string (using an extension), but I am not liking ThisIsAnEnum as an enum name (which is spaced out by the string extension) and it prohibits me from having longer names (which I need to maintain) such as for a radio button item. My goal is to have longer descriptions for radio button items without having to write really long enums. An extension/helper will probably be the right way to go, but I need to "fit" it into the code I am using, which is where I failed using the many examples out there.
The code I am using is generic, in that depending upon some logic either a radio button list, check box list, drop down list, select list or regular text boxes are displayed. For multi-item lists enum's are used, and the enum name is what is displayed (after using the string extension).
Here is the particular code that displays the enum:
public static IEnumerable<SelectListItem> GetItemsFromEnum<T>
(T selectedValue = default(T)) where T : struct
{
return from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = name.ProperCase(),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
ProperCase is the class that changes the enum to something readable.
I found something that almost worked:
public static string GetEnumDescription<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Description;
else
return value.ToString();
}
in which case I changed code from Text = name.ProperCase(), to Text = name.GetEnumDescription(...) but if I put value in the parenthesis I get a "does not exist in the current context" message (which I tried fixing but just made the problem worse). If I leave it blank I get the "No overload for ... takes 0 arguments" (again, understandable - but I don't know how to fix). And if I put name in the parenthesis the code compiles but upon viewing the page I get the "Object reference not set..." error on this line:
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes
(typeof(DescriptionAttribute), false);
I've spent a lot of time on this and know that my stumbling block is the
Text = name.ProperCase(),
code. Any ideas/help? Thanks in advance.
UPDATE:
If I do:
Text = GetEnumDescription(selectedValue),
I actually DO get the [Description] text, however, it just displays for the first enum. So, if I have 5 enums all with different [Description]'s the code just repeats the [Description] for the first enum 5 times instead of displaying differently for each. I hope that makes sense and gets to narrow down the problem.
I'd recommend you the Display attribute:
public static IEnumerable<SelectListItem> GetItemsFromEnum<T>(T selectedValue = default(T)) where T : struct
{
return
from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = GetEnumDescription(name, typeof(T)),
Value = enumValue,
Selected = name == selectedValue.ToString()
};
}
public static string GetEnumDescription(string value, Type enumType)
{
var fi = enumType.GetField(value.ToString());
var display = fi
.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (display != null)
{
return display.Name;
}
return value;
}
and then you could have:
public enum Foo
{
[Display(Name = "value 1")]
Value1,
Value2,
[Display(Name = "value 3")]
Value3
}
And now you could have:
var foo = Foo.Value2;
var values = GetItemsFromEnum(foo);
Also notice that I have modified the Selected clause in the LINQ expression as yours is not correct.
This being said, personally I would recommend you staying away from enums on your view models as they don't play nicely with what's built-in ASP.NET MVC and you will have to reinvent most of the things.

Resources