in CSharp/Library/Microsoft.Bot.Builder/FormFlow/Field.cs [52:819]
public delegate Task<bool> DefineAsyncDelegate<T>(T state, Field<T> field)
where T : class;
/// <summary>
/// A delegate for deciding on the next step in the form to execute.
/// </summary>
/// <typeparam name="T">Form state type.</typeparam>
/// <param name="value">Value just entered for field.</param>
/// <param name="state">Current state object.</param>
/// <returns></returns>
public delegate NextStep NextDelegate<T>(object value, T state)
where T : class;
/// <summary>Base class with declarative implementation of IField. </summary>
/// <typeparam name="T">Underlying form state.</typeparam>
public class Field<T> : IField<T>
where T : class
{
/// <summary> Construct field. </summary>
/// <param name="name"> Name of field. </param>
/// <param name="role"> Role field plays in form. </param>
public Field(string name, FieldRole role)
{
_name = name;
_role = role;
_min = -double.MaxValue;
_max = double.MaxValue;
_limited = false;
}
#region IField
public string Name { get { return _name; } }
public virtual IForm<T> Form
{
get { return this._form; }
set
{
_form = value;
foreach (var template in _form.Configuration.Templates)
{
if (!_templates.ContainsKey(template.Usage))
{
AddTemplate(template);
}
}
if (_define == null)
{
DefinePrompt();
DefineRecognizer();
}
}
}
#region IFieldState
public virtual object GetValue(T state)
{
throw new NotImplementedException();
}
public virtual void SetValue(T state, object value)
{
throw new NotImplementedException();
}
public virtual bool IsUnknown(T state)
{
throw new NotImplementedException();
}
public virtual void SetUnknown(T state)
{
throw new NotImplementedException();
}
public virtual Type Type
{
get { return _type; }
}
public virtual bool Optional
{
get
{
return _optional;
}
}
public virtual bool IsNullable
{
get
{
return _isNullable;
}
}
public virtual bool Limits(out double min, out double max)
{
min = _min;
max = _max;
return _limited;
}
public virtual string Pattern
{
get { return _pattern; }
}
public virtual IEnumerable<string> Dependencies
{
get
{
return _dependencies;
}
}
#endregion
#region IFieldDescription
public virtual FieldRole Role
{
get
{
return _role;
}
}
public virtual DescribeAttribute FieldDescription
{
get
{
return _description;
}
}
public virtual IEnumerable<string> FieldTerms
{
get
{
return _terms.Alternatives;
}
}
public virtual IEnumerable<string> Terms(object value)
{
return _valueTerms[value].Alternatives;
}
public virtual DescribeAttribute ValueDescription(object value)
{
return _valueDescriptions[value];
}
public virtual IEnumerable<DescribeAttribute> ValueDescriptions
{
get
{
return _valueDescriptions.Values;
}
}
public virtual IEnumerable<object> Values
{
get
{
return _valueDescriptions.Keys;
}
}
public virtual bool AllowsMultiple
{
get
{
return _allowsMultiple;
}
}
public virtual bool AllowDefault
{
get
{
return _promptDefinition.AllowDefault != BoolDefault.False;
}
}
public bool AllowNumbers
{
get
{
return _promptDefinition.AllowNumbers;
}
}
#endregion
#region IFieldResources
public virtual void SaveResources()
{
var localizer = _form.Resources;
if (_description.IsLocalizable)
{
localizer.Add(_name + nameof(_description), _description.Description);
localizer.Add(_name + nameof(_description.Image), _description.Image);
localizer.Add(_name + nameof(_description.Title), _description.Title);
localizer.Add(_name + nameof(_description.SubTitle), _description.SubTitle);
localizer.Add(_name + nameof(_description.Message), _description.Message);
}
if (_terms.IsLocalizable)
{
localizer.Add(_name + nameof(_terms), _terms.Alternatives);
}
localizer.Add(_name + nameof(_valueDescriptions), _valueDescriptions);
localizer.Add(_name + nameof(_valueTerms), _valueTerms);
if (_promptDefinition != null && _promptDefinition.IsLocalizable)
{
localizer.Add(_name + nameof(_promptDefinition), _promptDefinition.Patterns);
}
localizer.Add(_name, _templates);
}
public virtual void Localize()
{
var localizer = _form.Resources;
string strValue;
string[] terms;
if (localizer.Lookup(_name + nameof(_description), out strValue))
{
_description.Description = strValue;
}
if (localizer.Lookup(_name + nameof(_description.Image), out strValue))
{
_description.Image = strValue;
}
if (localizer.Lookup(_name + nameof(_description.Title), out strValue))
{
_description.Title = strValue;
}
if (localizer.Lookup(_name + nameof(_description.SubTitle), out strValue))
{
_description.SubTitle = strValue;
}
if (localizer.Lookup(_name + nameof(_description.Message), out strValue))
{
_description.Message = strValue;
}
if (localizer.LookupValues(_name + nameof(_terms), out terms))
{
_terms = new TermsAttribute(terms);
}
localizer.LookupDictionary(_name + nameof(_valueDescriptions), _valueDescriptions);
localizer.LookupDictionary(_name + nameof(_valueTerms), _valueTerms);
string[] patterns;
if (localizer.LookupValues(_name + nameof(_promptDefinition), out patterns))
{
_promptDefinition.Patterns = patterns;
}
localizer.LookupTemplates(_name, _templates);
if (!_promptSet)
{
_promptDefinition = null;
}
_prompt = null;
_recognizer = null;
if (_define == null)
{
DefinePrompt();
DefineRecognizer();
}
}
#endregion
#region IFieldPrompt
public virtual bool Active(T state)
{
return _condition(state);
}
public virtual TemplateAttribute Template(TemplateUsage usage)
{
TemplateAttribute template;
_templates.TryGetValue(usage, out template);
if (template != null)
{
template.ApplyDefaults(_form.Configuration.DefaultPrompt);
}
return template;
}
public virtual IPrompt<T> Prompt
{
get
{
return _prompt;
}
}
public async virtual Task<bool> DefineAsync(T state)
{
bool result = true;
if (_define != null)
{
if (!_promptSet)
{
_promptDefinition = null;
}
_recognizer = null;
_help = null;
_prompt = null;
result = await _define(state, this);
DefinePrompt();
DefineRecognizer();
}
return result;
}
public async virtual Task<ValidateResult> ValidateAsync(T state, object value)
{
var validateResult = await _validate(state, value);
if (validateResult.IsValid
&& value != null
&& (_type != null && (_type.IsAttachmentType() || _type.IsAttachmentCollection())))
{
if (_type.IsAttachmentType())
{
validateResult = await (value as AwaitableAttachment).ValidateAsync(this, state);
}
else
{
foreach (var awaitableAttachment in (value as IEnumerable<AwaitableAttachment>))
{
validateResult = await awaitableAttachment.ValidateAsync(this, state);
if (!validateResult.IsValid)
{
break;
}
}
// keeping original value (ie. IEnumerable<AwaitableAttachment>)
validateResult.Value = value;
}
}
return validateResult;
}
public virtual IPrompt<T> Help
{
get
{
return _help;
}
}
public virtual NextStep Next(object value, T state)
{
return _next(value, state);
}
#endregion
#endregion
#region Publics
/// <summary>Set the field description. </summary>
/// <param name="description">Field description. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetFieldDescription(string description)
{
_description = new DescribeAttribute(description);
return this;
}
/// <summary>
/// Set the full field description.
/// </summary>
/// <param name="description">The field description.</param>
/// <returns>A <see cref="Field{T}"/>. </returns>
public Field<T> SetFieldDescription(DescribeAttribute description)
{
_description = description;
return this;
}
/// <summary> Set the terms associated with the field. </summary>
/// <param name="terms"> The terms. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetFieldTerms(params string[] terms)
{
_terms = new TermsAttribute(terms);
return this;
}
/// <summary> Adds a description for a value. </summary>
/// <param name="value"> The value. </param>
/// <param name="description"> Description of the value. </param>
/// <param name="image">Image to use for value as button.</param>
/// <param name="message">Message to return when button is pressed.</param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> AddDescription(object value, string description, string image = null, string message = null)
{
_valueDescriptions[value] = new DescribeAttribute(description, image, message);
return this;
}
/// <summary> Adds a full description for a value. </summary>
/// <param name="value"> The value. </param>
/// <param name="description"> Description of the value. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> AddDescription(object value, DescribeAttribute description)
{
_valueDescriptions[value] = description;
return this;
}
/// <summary> Adds terms for a value. </summary>
/// <param name="value"> The value. </param>
/// <param name="terms"> The terms. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> AddTerms(object value, params string[] terms)
{
_valueTerms[value] = new TermsAttribute(terms);
return this;
}
/// <summary> Adds terms for a value. </summary>
/// <param name="value"> The value. </param>
/// <param name="terms"> The terms to add. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> AddTerms(object value, TermsAttribute terms)
{
_valueTerms[value] = terms;
return this;
}
/// <summary> Removes the description and terms associated with a value. </summary>
/// <param name="value"> The value to remove. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> RemoveValue(object value)
{
_valueDescriptions.Remove(value);
_valueTerms.Remove(value);
return this;
}
/// <summary> Removes all values and their associated descriptions and terms. </summary>
/// <returns> A <see cref="Field{T}"/>.</returns>
public Field<T> RemoveValues()
{
_valueDescriptions.Clear();
_valueTerms.Clear();
return this;
}
/// <summary> Sets the type of the underlying field state. </summary>
/// <param name="type"> The field type. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetType(Type type)
{
_type = type;
return this;
}
/// <summary> Set whether or not a field is optional. </summary>
/// <param name="optional"> True if field is optional. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetOptional(bool optional = true)
{
_optional = optional;
return this;
}
#region Documentation
/// <summary> Sets whether or not multiple values are allowed. </summary>
/// <param name="multiple"> True if multiple values are allowed. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
#endregion
public Field<T> SetAllowsMultiple(bool multiple = true)
{
_allowsMultiple = multiple;
return this;
}
/// <summary> Set whether or not field is nullable. </summary>
/// <param name="nullable"> True if field is nullable. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetIsNullable(bool nullable = true)
{
_isNullable = nullable;
return this;
}
#region Documentation
/// <summary> Define a delegate for checking state to see if field applies. </summary>
/// <param name="condition"> The condition delegate. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
#endregion
public Field<T> SetActive(ActiveDelegate<T> condition)
{
if (condition != null) _condition = condition;
return this;
}
#region Documentation
/// <summary> Define a delegate for dynamically defining field. </summary>
/// <param name="definition"> The definition delegate. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
/// <remarks>When you dynamically define a field through this delegate you can use all of the fluent methods
/// defined on <see cref="Field{T}"/> to change the descriptions and terms dynamically.</remarks>
#endregion
public Field<T> SetDefine(DefineAsyncDelegate<T> definition)
{
if (definition != null) _define = definition;
return this;
}
/// <summary> Sets the field prompt. </summary>
/// <param name="prompt"> The prompt. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetPrompt(PromptAttribute prompt)
{
_promptDefinition = prompt;
_promptSet = true;
return this;
}
/// <summary> Sets the recognizer for the field. </summary>
/// <param name="recognizer"> The recognizer for the field. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
/// <remarks>
/// This should only be called when you are dynamically defining a field using a <see cref="DefineAsyncDelegate{T}"/> because
/// recognizers usually require the field and often change if the localization changes.
/// </remarks>
public Field<T> SetRecognizer(IRecognize<T> recognizer)
{
_recognizer = recognizer;
_buildPrompts = true;
return this;
}
/// <summary> Replace a template in the field. </summary>
/// <param name="template"> The template. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> ReplaceTemplate(TemplateAttribute template)
{
AddTemplate(template);
return this;
}
/// <summary> Set the field validation. </summary>
/// <param name="validate"> The validator. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetValidate(ValidateAsyncDelegate<T> validate)
{
if (validate != null) _validate = validate;
return this;
}
/// <summary> Set numeric limits. </summary>
/// <param name="min"> The minimum. </param>
/// <param name="max"> The maximum. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetLimits(double min, double max)
{
SetLimits(min, max, true);
return this;
}
/// <summary>
/// Regular expression for validating strings.
/// </summary>
/// <param name="pattern">Validation regular expression.</param>
/// <returns> A <see cref="Field{T}"/>. </returns>
public Field<T> SetPattern(string pattern)
{
_pattern = pattern;
var regex = new Regex(pattern, RegexOptions.Compiled);
_validate = async (T state, object value) =>
{
var result = new ValidateResult { Value = value };
if (value == null)
{
result.IsValid = _optional;
}
else
{
var match = regex.Match((string)value);
result.IsValid = match.Success;
}
if (!result.IsValid)
{
result.Feedback = new Prompter<T>(Template(TemplateUsage.NotUnderstood), _form, null).Prompt(state, this, value).Prompt;
}
return result;
};
return this;
}
#region Documentation
/// <summary> Define the fields this field depends on. </summary>
/// <param name="dependencies"> A variable-length parameters list containing dependencies. </param>
/// <returns> A <see cref="Field{T}"/>. </returns>
#endregion
public Field<T> SetDependencies(params string[] dependencies)
{
_dependencies = dependencies;
return this;
}
/// <summary>
/// Delegate for deciding on the next form step to execute.
/// </summary>
/// <returns>A <see cref="Field{T}"/>.</returns>
public Field<T> SetNext(NextDelegate<T> next)
{
_next = next;
return this;
}
#endregion
#region Internals
protected void DefinePrompt()
{
if (_promptDefinition == null)
{
TemplateUsage usage = TemplateUsage.None;
if (_type == null || _type.IsEnum)
{
usage = _allowsMultiple ? TemplateUsage.EnumSelectMany : TemplateUsage.EnumSelectOne;
}
else if (_type == typeof(string))
{
usage = TemplateUsage.String;
}
else if (_type.IsIntegral())
{
usage = TemplateUsage.Integer;
}
else if (_type == typeof(bool))
{
usage = TemplateUsage.Bool;
}
else if (_type.IsDouble())
{
usage = TemplateUsage.Double;
}
else if (_type == typeof(DateTime))
{
usage = TemplateUsage.DateTime;
}
else if (_type.IsAttachmentType())
{
usage = TemplateUsage.AttachmentField;
}
else if (_type.IsAttachmentCollection())
{
usage = TemplateUsage.AttachmentCollection;
}
else
{
throw new ArgumentException($"{_name} is not a type FormFlow understands.");
}
if (usage != TemplateUsage.None)
{
_promptDefinition = new PromptAttribute(Template(usage));
}
_promptSet = false;
}
_promptDefinition.ApplyDefaults(_form.Configuration.DefaultPrompt);
}
protected void DefineRecognizer()
{
if (_recognizer == null)
{
if (_type == null || _type.IsEnum)
{
_recognizer = new RecognizeEnumeration<T>(this);
}
else if (_type == typeof(bool))
{
_recognizer = new RecognizeBool<T>(this);
}
else if (_type == typeof(string))
{
_recognizer = new RecognizeString<T>(this);
}
else if (_type.IsIntegral())
{
_recognizer = new RecognizeNumber<T>(this);
}
else if (_type.IsDouble())
{
_recognizer = new RecognizeDouble<T>(this);
}
else if (_type == typeof(DateTime))
{
_recognizer = new RecognizeDateTime<T>(this);
}
else if (_type.IsAttachmentType() || _type.IsAttachmentCollection())
{
_recognizer = new RecognizeAttachment<T>(this, _type.IsAttachmentCollection());
}
else if (_type.IsIEnumerable())
{
var elt = _type.GetGenericElementType();
if (elt.IsEnum)
{
_recognizer = new RecognizeEnumeration<T>(this);
}
}
_buildPrompts = true;
}
if (_buildPrompts)
{
var template = Template(TemplateUsage.Help);
_help = new Prompter<T>(template, _form, _recognizer);
var prompt = _promptDefinition;
_prompt = new Prompter<T>(_promptDefinition, _form, _recognizer);
_buildPrompts = false;
}
}
protected void SetLimits(double min, double max, bool limited)
{
_min = min;
_max = max;
_limited = limited;
}
protected void AddTemplate(TemplateAttribute template)
{
_templates[template.Usage] = template;
}
protected IForm<T> _form;
protected string _name;
protected FieldRole _role;
protected ActiveDelegate<T> _condition = new ActiveDelegate<T>((state) => true);
protected DefineAsyncDelegate<T> _define = null;
protected ValidateAsyncDelegate<T> _validate = new ValidateAsyncDelegate<T>(async (state, value) => new ValidateResult { IsValid = true, Value = value });
protected NextDelegate<T> _next = new NextDelegate<T>((value, state) => new NextStep());
protected double _min, _max;
protected bool _limited;
protected string _pattern;
protected string[] _dependencies = Array.Empty<string>();
protected bool _allowsMultiple;
protected Type _type;
protected bool _optional;
protected bool _isNullable;
protected bool _keepZero;
protected DescribeAttribute _description = new DescribeAttribute(null);
protected TermsAttribute _terms = new TermsAttribute();
protected Dictionary<object, DescribeAttribute> _valueDescriptions = new Dictionary<object, DescribeAttribute>();
protected Dictionary<object, TermsAttribute> _valueTerms = new Dictionary<object, TermsAttribute>();
protected Dictionary<TemplateUsage, TemplateAttribute> _templates = new Dictionary<TemplateUsage, TemplateAttribute>();
protected bool _promptSet;
protected PromptAttribute _promptDefinition;
protected bool _buildPrompts = true;
protected IRecognize<T> _recognizer;
protected IPrompt<T> _help;
protected IPrompt<T> _prompt;
#endregion
}