forms: make IField.Validate and IStep.Process async Task methods

This commit is contained in:
Will Portnoy 2016-03-25 15:06:42 -07:00
Родитель 2862b2ea6a
Коммит 2d18fc7447
9 изменённых файлов: 79 добавлений и 41 удалений

Просмотреть файл

@ -37,9 +37,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Form.Advanced
{
#pragma warning disable CS1998
/// <summary>Base class with declarative implementation of <see cref="IField{T}"/>. </summary>
/// <typeparam name="T">Underlying form state.</typeparam>
public class Field<T> : IField<T>
@ -199,7 +202,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
return _prompt;
}
public virtual string Validate(T state, object value)
public virtual Task<string> ValidateAsync(T state, object value)
{
return _validate(state, value);
}
@ -382,7 +385,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
protected bool _keepZero;
protected string _description;
protected Prompt _help;
protected ValidateDelegate<T> _validate = new ValidateDelegate<T>((state, value) => null);
protected ValidateDelegate<T> _validate = new ValidateDelegate<T>(async (state, value) => null);
protected string[] _terms = new string[0];
protected Dictionary<object, string> _valueDescriptions = new Dictionary<object, string>();
protected Dictionary<object, string[]> _valueTerms = new Dictionary<object, string[]>();

Просмотреть файл

@ -154,7 +154,7 @@ namespace Microsoft.Bot.Builder.Form
IFormBuilder<T> IFormBuilder<T>.Confirm(Prompt prompt, ConditionalDelegate<T> condition, IEnumerable<string> dependencies)
{
if (condition == null) condition = (state) => true;
if (condition == null) condition = state => true;
if (dependencies == null)
{
// Default next steps go from previous field ignoring confirmations back to next confirmation
@ -200,7 +200,7 @@ namespace Microsoft.Bot.Builder.Form
return this;
}
IFormBuilder<T> IFormBuilder<T>.OnCompletion(CompletionDelegate<T> callback)
IFormBuilder<T> IFormBuilder<T>.OnCompletionAsync(CompletionDelegate<T> callback)
{
_form._completion = callback;
return this;

Просмотреть файл

@ -43,6 +43,8 @@ using Microsoft.Bot.Builder.Form.Advanced;
namespace Microsoft.Bot.Builder.Form
{
#pragma warning disable CS1998
internal static class FormStatics
{
#region IForm<T> statics
@ -175,7 +177,9 @@ namespace Microsoft.Bot.Builder.Form
// 1) Go through them while supporting only quit or back and reset
// 2) Drop them
// 3) Just pick one (found in form.StepState, but that is opaque here)
step.Process(context, _state, _formState, input, matches, out feedback, out prompt);
var result = await step.ProcessAsync(context, _state, _formState, input, matches);
feedback = result.Feedback;
prompt = result.Prompt;
}
else
{
@ -269,7 +273,11 @@ namespace Microsoft.Bot.Builder.Form
matches = MatchAnalyzer.Coalesce(matches, lastInput).ToArray();
if (MatchAnalyzer.IsFullMatch(lastInput, matches))
{
next = step.Process(context, _state, _formState, lastInput, matches, out feedback, out prompt);
var result = await step.ProcessAsync(context, _state, _formState, lastInput, matches);
next = result.Next;
feedback = result.Feedback;
prompt = result.Prompt;
// 1) Not completed, not valid -> Not require, last
// 2) Completed, feedback -> require, not last
requirePrompt = (_formState.Phase() == StepPhase.Completed);
@ -304,7 +312,11 @@ namespace Microsoft.Bot.Builder.Form
var bestMatch = MatchAnalyzer.BestMatches(matches, commands);
if (bestMatch == 0)
{
next = step.Process(context, _state, _formState, lastInput, matches, out feedback, out prompt);
var result = await step.ProcessAsync(context, _state, _formState, lastInput, matches);
next = result.Next;
feedback = result.Feedback;
prompt = result.Prompt;
requirePrompt = (_formState.Phase() == StepPhase.Completed);
useLastPrompt = !requirePrompt;
}

Просмотреть файл

@ -34,6 +34,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Form.Advanced
{
@ -334,7 +335,7 @@ namespace Microsoft.Bot.Builder.Form.Advanced
/// One way to control this is to supply a <see cref="ValidationDelegate"/> to the
/// <see cref="IForm<T>.Field"/> or <see cref="IForm<T>.Confirm"/> steps.
/// </remarks>
string Validate(T state, object value);
Task<string> ValidateAsync(T state, object value);
/// <summary>
/// Return the help description for this field.

Просмотреть файл

@ -185,6 +185,6 @@ namespace Microsoft.Bot.Builder.Form
/// the form state results. In any case the completed form state will be passed
/// to the parent dialog.
/// </remarks>
IFormBuilder<T> OnCompletion(CompletionDelegate<T> callback);
IFormBuilder<T> OnCompletionAsync(CompletionDelegate<T> callback);
}
}

Просмотреть файл

@ -35,6 +35,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Bot.Builder.Form.Advanced;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Form
{
@ -53,7 +54,7 @@ namespace Microsoft.Bot.Builder.Form
/// <param name="state">Form state to test.</param>
/// <param name="value">Response value to validate.</param>
/// <returns>Null if value is valid otherwise feedback on what is wrong.</returns>
public delegate string ValidateDelegate<T>(T state, object value);
public delegate Task<string> ValidateDelegate<T>(T state, object value);
/// <summary>
/// A delegate called when a form is completed.
@ -66,7 +67,7 @@ namespace Microsoft.Bot.Builder.Form
/// such as sending it to your service. It cannot be used to create a new
/// dialog or return a value to the parent dialog.
/// </remarks>
public delegate void CompletionDelegate<T>(IDialogContext context, T state);
public delegate Task CompletionDelegate<T>(IDialogContext context, T state);
/// <summary>
/// Interface for controlling the form dialog created.

Просмотреть файл

@ -32,6 +32,7 @@
//
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Form.Advanced;
@ -39,6 +40,21 @@ namespace Microsoft.Bot.Builder.Form
{
internal enum StepPhase { Ready, Responding, Completed };
internal enum StepType { Field, Confirm, Navigation, Message };
internal struct StepResult
{
internal StepResult(NextStep next, string feedback, string prompt)
{
this.Next = next;
this.Feedback = feedback;
this.Prompt = prompt;
}
internal NextStep Next { get; set; }
internal string Feedback { get; set; }
internal string Prompt { get; set; }
}
internal interface IStep<T>
{
string Name { get; }
@ -53,8 +69,7 @@ namespace Microsoft.Bot.Builder.Form
IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, string input, out string lastInput);
NextStep Process(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches,
out string feedback, out string prompt);
Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches);
string NotUnderstood(IDialogContext context, T state, FormState form, string input);

Просмотреть файл

@ -37,9 +37,12 @@ using System.Diagnostics;
using System.Linq;
using Microsoft.Bot.Builder.Form.Advanced;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Form
{
#pragma warning disable CS1998
internal class FieldStep<T> : IStep<T>
{
public FieldStep(string name, IForm<T> form)
@ -115,11 +118,12 @@ namespace Microsoft.Bot.Builder.Form
return matches;
}
public NextStep Process(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches,
out string feedback, out string prompt)
public async Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches)
{
feedback = null;
prompt = null;
string feedback = null;
string prompt = null;
var iprompt = _field.Prompt();
var fieldState = form.StepState as FieldStepState;
object response = null;
@ -129,7 +133,8 @@ namespace Microsoft.Bot.Builder.Form
var firstMatch = matches.FirstOrDefault();
if (matches.Count() == 1)
{
response = SetValue(state, firstMatch.Value, form, out feedback);
response = firstMatch.Value;
feedback = await SetValueAsync(state, response, form);
}
else if (matches.Count() > 1)
{
@ -187,12 +192,14 @@ namespace Microsoft.Bot.Builder.Form
{
if (_field.AllowsMultiple())
{
response = SetValue(state, settled, form, out feedback);
response = settled;
feedback = await SetValueAsync(state, response, form);
}
else
{
Debug.Assert(settled.Count() == 1);
response = SetValue(state, settled.First(), form, out feedback);
response = settled.First();
feedback = await SetValueAsync(state, response, form);
}
}
}
@ -231,12 +238,14 @@ namespace Microsoft.Bot.Builder.Form
// No clarification left, so set the field
if (_field.AllowsMultiple())
{
response = SetValue(state, fieldState.Settled, form, out feedback);
response = fieldState.Settled;
feedback = await SetValueAsync(state, response, form);
}
else
{
Debug.Assert(fieldState.Settled.Count() == 1);
response = SetValue(state, fieldState.Settled.First(), form, out feedback);
response = fieldState.Settled.First();
feedback = await SetValueAsync(state, response, form);
}
form.SetPhase(StepPhase.Completed);
}
@ -257,7 +266,8 @@ namespace Microsoft.Bot.Builder.Form
}
}
}
return _field.Next(response, state);
var next = _field.Next(response, state);
return new StepResult(next, feedback, prompt);
}
public string NotUnderstood(IDialogContext context, T state, FormState form, string input)
@ -351,16 +361,16 @@ namespace Microsoft.Bot.Builder.Form
return value;
}
private object SetValue(T state, object value, FormState form, out string feedback)
private async Task<string> SetValueAsync(T state, object value, FormState form)
{
var desc = _field.Form.Fields.Field(_name);
feedback = desc.Validate(state, value);
var feedback = await desc.ValidateAsync(state, value);
if (feedback == null)
{
SetValue(state, value);
form.SetPhase(StepPhase.Completed);
}
return value;
return feedback;
}
private IPrompt<T> NextClarifyPrompt(T state, FieldStepState stepState, IRecognize<T> recognizer, out Ambiguous clarify)
@ -471,16 +481,13 @@ namespace Microsoft.Bot.Builder.Form
return prompter.Prompt(state, "", input);
}
public NextStep Process(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches,
out string feedback,
out string prompt)
public async Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches)
{
feedback = null;
prompt = null;
var value = matches.First().Value;
form.StepState = null;
form.SetPhase((bool)value ? StepPhase.Completed : StepPhase.Ready);
return _field.Next(value, state);
var next = _field.Next(value, state);
return new StepResult(next, feedback: null, prompt: null);
}
public string Start(IDialogContext context, T state, FormState form)
@ -575,14 +582,11 @@ namespace Microsoft.Bot.Builder.Form
return new Prompter<T>(template, _form, null).Prompt(state, _name, input);
}
public NextStep Process(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches,
out string feedback,
out string prompt)
public async Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches)
{
feedback = null;
prompt = null;
form.Next = null;
return new NextStep(new string[] { matches.First().Value as string });
var next = new NextStep(new string[] { matches.First().Value as string });
return new StepResult(next, feedback: null, prompt: null);
}
public string Start(IDialogContext context, T state, FormState form)
@ -671,7 +675,7 @@ namespace Microsoft.Bot.Builder.Form
throw new NotImplementedException();
}
public NextStep Process(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches, out string feedback, out string prompt)
public Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, string input, IEnumerable<TermMatch> matches)
{
throw new NotImplementedException();
}

Просмотреть файл

@ -43,6 +43,8 @@ using AnnotatedSandwichOrder = Microsoft.Bot.Sample.AnnotatedSandwichBot.Sandwic
namespace Microsoft.Bot.Builder.FormTest
{
#pragma warning disable CS1998
public enum DebugOptions
{
None, AnnotationsAndNumbers, AnnotationsAndNoNumbers, NoAnnotations, NoFieldOrder,
@ -114,7 +116,7 @@ namespace Microsoft.Bot.Builder.FormTest
.Message("What we have is a {?{Signature} signature pizza} {?{GourmetDelite} gourmet pizza} {?{Stuffed} {&Stuffed}} {?{?{BYO.Crust} {&BYO.Crust}} {?{BYO.Sauce} {&BYO.Sauce}} {?{BYO.Toppings}}} pizza")
.Field("DeliveryAddress", validate:
(state, value) =>
async (state, value) =>
{
string feedback = null;
var str = value as string;
@ -129,7 +131,7 @@ namespace Microsoft.Bot.Builder.FormTest
.Confirm("Would you like a {Size}, {&Signature} {Signature} pizza delivered to {DeliveryAddress}?", isSignature, dependencies: new string[] { "Size", "Kind", "Signature" })
.Confirm("Would you like a {Size}, {&GourmetDelite} {GourmetDelite} pizza delivered to {DeliveryAddress}?", isGourmet)
.Confirm("Would you like a {Size}, {&Stuffed} {Stuffed} pizza delivered to {DeliveryAddress}?", isStuffed)
.OnCompletion((session, pizza) => Console.WriteLine("{0}", pizza))
.OnCompletionAsync(async (session, pizza) => Console.WriteLine("{0}", pizza))
.Build();
}