Validation of the User's Response
This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.
After the user responds to a prompt from the application and the application confirms the response, an application can choose to validate the response. By confirming the user's response, the application ensures that the user's utterance matches the grammar in use and provides an opportunity for the user to confirm that the recognized value agrees with the utterance.
For business reasons, an application might decide that some user utterances, although consistent with the grammar, are invalid. Consider, for example, a simple banking application in which users can make withdrawals from their accounts. Any request to withdraw an amount of money larger than the current account balance should be considered invalid, even though the spoken request matched the grammar rule and the user confirmed the amount.
Using a ValidatorActivity within a FormFillingDialogActivity
The example in this topic uses a FormFillingDialogActivity that contains two QuestionAnswerActivity objects and a ValidatorActivity object. The first QuestionAnswerActivity object, askCity, asks the user to supply the name of a city. The second QuestionAnswerActivity, confirmCity, confirms that the recognized city name is the correct one. The ValidatorActivity object, validateCity, ensures that the recognized city is actually valid. In the following illustration, one of the city names is hard-coded in a list of invalid choices. A more realistic application would likely query a database to verify that the user's choice is valid.
Dialog Flow
The following illustration, from Workflow Designer, depicts in visual form the dialog flow for the activities described in the previous section. The FormFillingDialogActivity object that contains askCity, confirmCity, and validateCity remains active as long as at least one of its child activities remains active. This means that the activities within formFillingDialogActivity1 can execute repeatedly.
Application Data
The data used in the application consists of a semantic item and an array of city names. The first line declares the semantic item, _city. The second line creates the _cityList array, whose elements are the names of eight cities in the United States. The third line creates an array that contains the names of cities that are invalid.
private SemanticItem<string> _city;
private string[] _cityList = new string[] { "Albany", "Butte", "Chicago", "Dover", "Ephrata", "Ferndale", "Grand Rapids", "Helena"};
private string[] _invalidCityList = new string[] {"Ephrata"};
Support Code for the Workflow Activities
The workflow constructor is shown next. Besides the call to InitializeComponent, which is automatically generated by the speech project template, the added code creates a semantic item variable (_city), adds it to the Answers collection on askCity and to the Confirms collection on confirmCity, and uses it to set the SemanticItem property on validateCity.
The final two lines of code are associated event handlers with the Closed members on askCity and confirmCity. The two event handlers are shown later in this topic.
public Workflow1()
{
InitializeComponent();
_city = new SemanticItem<string>(this, "city");
askCity.Answers.Add(_city);
confirmCity.Confirms.Add(_city);
validateCity.SemanticItem = _city;
this.askCity.Closed += new System.EventHandler<System.Workflow.ComponentModel.ActivityExecutionStatusChangedEventArgs>(this.askCity_Closed);
this.confirmCity.Closed += new System.EventHandler<System.Workflow.ComponentModel.ActivityExecutionStatusChangedEventArgs>(this.confirmCity_Closed);
}
The following method is the handler for the TurnStarting event on askCity. Each time TurnStarting is raised, this method clears the Grammars property on askCity and creates a grammar that consists of the strings in _cityList that are not also in the semantic item's DeniedValues collection. The newly created grammar is then added to the Grammars collection on askCity.
After the grammar is added, this method constructs its main prompt from the _cityList elements (as before, omitting any that are in the semantic item's DeniedValues collection). The method is able to limit the cities listed in its main prompt and constrain the user's response by constructing a grammar that recognizes only the cities listed in the main prompt.
private void askCity_TurnStarting(object sender, TurnStartingEventArgs e)
{
// Create a grammar dynamically, using the _cityList array and the DeniedValues collection.
this.askCity.Grammars.Clear();
this.askCity.Grammars.Add(new Grammar(CreateGrammar(_cityList, _city)));
this.askCity.MainPrompt.SetText("Choose the name of a city from the following list ");
this.askCity.MainPrompt.AppendBreak(TimeSpan.FromMilliseconds(150));
// Iterate through the _cityList array, prompting for those cities not on the
// DeniedValues list. There is no logic here for the case where all of the cities
// have been denied.
for (int idx = 0; idx < _cityList.Length; idx++)
{
if (_city.DeniedValues.Count == 0 ||
!_city.DeniedValues.Contains(_cityList[idx]))
{
this.askCity.MainPrompt.AppendText(_cityList[idx]);
this.askCity.MainPrompt.AppendBreak(TimeSpan.FromMilliseconds(150));
}
}
}
When askCity raises the Closed event, the following event handler is executed. Its purpose is to set the Value and State properties on the _city semantic item.
private void askCity_Closed(object sender, EventArgs e)
{
if (this.askCity.RecognitionResult != null)
{
_city.Value = this.askCity.RecognitionResult.Semantics.Value.ToString();
_city.State = SemanticItemState.NeedsConfirmation;
}
}
CreateGrammar is a helper method that takes an array of type string and a semantic item and returns a grammar rule in the form of an SrgsDocument. The rule consists of a number of one-of elements, one for each element of the array that is not also in the semantic item's DeniedValues collection.
SrgsDocument CreateGrammar(string[] list, SemanticItem<string> si)
{
// Create an array whose size is the number of valid cities.
// The valid cities are those in the list array that aren't
// also in the semantic item's DeniedValues collection.
int sizeOfGrammar = list.Length - si.DeniedValues.Count;
string[] validCities = new string[sizeOfGrammar];
// If the city is not in the DeniedValues collection,
// copy it to the validCities array.
for(int i = 0, j = 0; i < sizeOfGrammar; i++, j++)
{
// City in list[j] is not in DeniedValues collection.
if (!si.DeniedValues.Contains(list[j]))
{
validCities[i] = list[j];
}
// City in list[j] is in DeniedValues collection,
// so decrement i. Since both i and j will be
// incremented in next iteration, the net effect
// is that i remains fixed while j is incremented
// in the next loop iteration.
else
{
i--;
}
}
// Create a grammar consisting of cities in validCities array
SrgsOneOf oneOf;
SrgsRule rule;
SrgsDocument citiesGrammar = new SrgsDocument();
oneOf = new SrgsOneOf(validCities);
rule = new SrgsRule("City", oneOf);
citiesGrammar.Rules.Add(rule);
citiesGrammar.Root = rule;
return citiesGrammar;
}
The next method is the handler for the TurnStarting event on confirmCity. This method restates the user's choice of city and asks whether that is correct.
private void confirmCity_TurnStarting(object sender, TurnStartingEventArgs e)
{
if (this.askCity.RecognitionResult.Semantics != null )
{
this.confirmCity.MainPrompt.SetText(string.Format("You chose {0}", _city.Value));
this.confirmCity.MainPrompt.AppendBreak(TimeSpan.FromMilliseconds(150));
this.confirmCity.MainPrompt.AppendText("Is that correct?");
}
}
When confirmCity raises the Closed event, the following event handler determines whether the user answered "Yes" to confirm the previous choice of city. If so, this method calls Accept on the semantic item and calls Deny otherwise. The call to Deny causes the value of the semantic item to be added to the DeniedValues collection on the semantic item.
private void confirmCity_Closed(object sender, EventArgs e)
{
if (this.confirmCity.RecognitionResult.Semantics.Value.ToString().Equals("Yes"))
{
_city.Accept();
}
else
{
_city.Deny();
}
}
The following method is the handler for the Validating event on validateCity. All it does is call a helper method that determines whether the value of the _city semantic item is valid. The method sets Valid to the value returned by isValidCity.
private void validateCity_Validating(object sender, ValidationEventArgs e)
{
e.Valid = isValidCity(_city.Value);
}
validateCity_Validating calls the following method to determine whether the city specified in the call is valid. The method does this by iterating through the _invalidCityList array, comparing each name in the array with the passed-in argument. If the method argument matches one of the array elements, the method immediately returns false. If the loop completes without finding a match, the method returns true. As noted earlier, a working application probably get this information by querying a database.
private bool isValidCity(string city)
{
int len = _invalidCityList.Length;
if (len > 0)
{
for (int idx = 0; idx < len; idx++)
{
if (_invalidCityList[idx].Equals((string) city))
{
return false;
}
}
}
return true;
}
The next handler is called when the TurnStarting event is raised on validateCity. This occurs when Valid is set to false in the handler for the Validating event, shown previously.
private void validateCity_TurnStarting(object sender, TurnStartingEventArgs e)
{
this.validateCity.MainPrompt.SetText("Sorry, that city is no longer valid.");
}
The final method is the handler for TurnStarting on sayGoodbye.
private void sayGoodbye_TurnStarting(object sender, TurnStartingEventArgs e)
{
this.sayGoodbye.MainPrompt.SetText("Goodbye");
}