< Day Day Up > |
Designing a User Navigation PathBefore jumping in and writing code for a voice-only application, it is a good idea to determine the exact user navigation paths. Microsoft Visio was used to design the navigation flow for this chapter, but any tool that allows you to graphically design program functionality would work fine. The Slugger Sports application allows the salesperson to retrieve two crucial types of information: new opportunities and product information. For telephony applications, the user is guided through the application with a series of questions and answers; therefore it is important to design simple and intuitive navigation. Figure 3.3 illustrates the possible paths a salesperson can take. After being welcomed to the system, the salesperson will be prompted to say a first and last name. The application will then repeat the name it recognized and ask if it is correct. If the response is yes, the salesperson will be asked to enter a password code. Figure 3.3. User flow for the voice-only sample application. By determining the navigation path before code is written, you can reduce the need for rewrites and determine what prompts are needed. Each shaded box represents an opportunity to both prompt a user and receive a response.Note For the sample application, users are asked to say their first and last names in order to identify themselves. Since Slugger Sports is a small company with a limited number of salespeople, this is a viable method. For large companies that support hundreds or thousands of employees, it may be more practical to ask the user to say a unique identifier, such as an employee number. The password code can be entered via the keypad or spoken as text. Allowing the user to enter via the keypad prevents anyone from overhearing the password code if it is spoken in a public area. If the password code entered exists in the database, the salesperson is directed to the main menu. The salesperson is then asked what function they wish to perform. The system will list the possible functions and ask the user to respond with a
Note Asking the salesperson to specify numbers instead of complete commands reduces the chance of error. However, utilizing numbered responses instead of voice commands makes the application less natural. This is a tradeoff to consider when designing a voice-only application. A salesperson who wishes to get product info is asked for a product name. If the product name is found in the database, all current product information is listed. This information includes the number of units in stock, number of units on order, and unit price. A salesperson who wishes to get new opportunities is first asked whether he or she has moved to a new area. Unexpected events can sometimes cause this to happen. If the salesperson is in the same area, the system looks for all leads in that area with a status of 'N'. All functions end with a listing recited to the salesperson. To navigate through this listing, the salesperson can use the commands Next, Previous, and Read. Determining the flow before the code is written can help reduce the need for code rewrites. Documented flows, even if handwritten, can also help to determine what recorded prompts are needed. This can be especially helpful when designing the prompt database. Loading the Sample ApplicationTable 3.1 contains a listing of the two project files that comprise the sample application. To execute the sample code provided on the book's Web site, you will need to execute the following steps:
Examining the Startup Web PageThe startup page for the sample application is Login.aspx. This page will handle the initial validation of the salesperson by asking for a user name and password code. Validation is accomplished by executing server-side code and calling the VerifyPasscode function. VerifyPasscode, found in the Login.vb code file, executes the VerifyLogin SQL Server stored procedure. Once validated, the user will be redirected to the MainMenu.aspx page. The first control associated with Login.aspx (see Figure 3.4) is a user control that references the Common.ascx file. This control groups together the common navigational commands Help, Repeat, and Main Menu. The commands are placed in a user control so they can be reused in other pages. This group also contains Include statements that point to JScript files containing commonly used client-side functions. Figure 3.4. Partial Screenshot of the Design view for Login.aspx (the startup Web page). Since the sample application is voice-only, no visible controls are needed.
The second control on the page is the SemanticMap. This control contains information about all the semantic items used on the page. Normally there will be one semantic item associated with each piece of information retrieved from the user. For the login page, there will be two: siUserName and siPasscode. Semantic items can be viewed and edited by selecting the control and clicking the Property Builder link inside of Properties. The first control that initiates an interaction with the user is the AnswerCall control. The next control, named WelcomeQA, is used to read a welcome message one time. This control is the only one that utilizes an Inline prompt. All the other controls will utilize prompt functions. Although you have the option of using an Inline prompt for all controls, it is recommended that you not do so. Inline prompting involves hard-coding the prompt used for the control. It is inflexible and can present a maintenance problem in your application. Using Prompt FunctionsA voice-only application will typically contain several prompt functions. Prompt functions represent client-side Jscript code that is used to generate dynamic prompts depending on the flow of dialog. For our sample application, the control that begins the first dialog with the user is AskUserNameQA. To view the prompt associated with this control, click on the PropertyBuilder link at the bottom of the Properties box. Click the General tab under Voice Output. From here, you will see that the control uses a prompt function named AskUserNameQA_prompt (see Figure 3.5). You can go directly to the code for this function by clicking Edit prompt function file. You can also access the function by double-clicking the file Login.pf from Solution Explorer. Figure 3.5. Screenshot of the Property Builder dialog as it displays properties for the AskUserNameQA control. This control uses a prompt function named AskUserNameQA_Prompt.Note By default, the SASDK creates a prompt function file (with a .pf extension) with the same name as the Web page. This is done the first time the developer clicks <New. . .> from the Prompt Options dialog (see Figure 3.5). To prevent code redundancy, the application utilizes a file named PromptGenerator.js. This javascript class includes code used by all QA controls to generate prompts. Utilizing the Generate function makes it unnecessary to include code for handling commands such as Help and Repeat within each prompt function. function PromptGenerator.Generate(History, text, help) { switch (History[History.length - 1]) { case "NoReco": if (History.length > 1) return "Sorry, I still don't understand you. " + help; else return "Sorry, I am having trouble understanding you. If you need help, say help. " + text; case "Silence": if (History.length > 1) return "Sorry, I still don't hear you. " + help; else return "Sorry, I am having trouble hearing you. If you need help, say help. " + text; case "Help": PromptGenerator.RepeatPrompt = help; return help; case "Repeat": return "I repeat: " + PromptGenerator.RepeatPrompt; default: PromptGenerator.RepeatPrompt = text; return text; } } The function accepts three parameters: history, text, and help. These parameters allow you to control what prompts are displayed depending on the user's response. A user who does not respond before the InitialTimeout property expires will be prompted with "Sorry, I am having trouble hearing you. If you need help, say help." For subsequent silence periods, it will respond with "Sorry, I still don't hear you" along with the help string from the calling prompt function. Prompt functions allow you to vary what is prompted to the user depending on the situation. This helps to create a more dynamic and natural-sounding dialog. Instead of hearing the same phrase repeated endlessly, the user will get different prompts. Building a Dynamic GrammarThe QA control represents a specific interaction with the user. This includes not only the prompt but the grammar as well. The grammar associated with the AskUserNameQA control is username.grxml. This is seen by viewing the Grammar tab in Property Builder. Since the user name is the name of the employee, and employee turnover at Slugger Sports is high, this grammar is created dynamically. The username.grxml file is rewritten every time the login page is loaded. Since dynamic grammars will be utilized more than once in this application, a central function named LoadGrammarFile was placed in the DynamicGrammars class file (available within the Components folder of Solution Explorer). LoadGrammarFile accepts three parameters: the file location, the node name to be used in the grammar file, and the SQL Server stored procedure name used to access the data from the SalesScheduling database. By utilizing a central function, we do not have to repeat identical code throughout the application. Dynamic grammars will be used when requesting the user names and product names. The following code snippet shows the contents of the LoadGrammarFile method. This method accepts as parameters the file location, the name to be shown in the SML, and the stored procedure used to access the data. Public Shared Sub LoadGrammarFile(ByVal fileloc As String, _ ByVal Name As String, ByVal sp As String) 'Define location for resulting grammar file Dim xmlFileOutputLoc As String = fileloc Dim sb As New StringBuilder 'Define the high level tags used in the grammar file sb.Append( _ "<grammar xml:lang=""en-US"" tag-format=""semantics-ms/1.0"" " _ & "version=""1.0"" root=""" + Name + "Rule"" mode=""voice"" " _ & "xmlns=""http://www.w3.org/2001/06/grammar"">") sb.Append("<rule " + Name + "Rule"" scope=""public"">") 'Define one-of elements which indicates a list sb.Append("<one-of>") Dim dr As SqlDataReader = SqlHelper.ExecuteReader( _ ConfigurationSettings.AppSettings("Chapter3.Connection"), _ CommandType.StoredProcedure, sp) 'Loop through the Datareader Do While dr.Read() sb.Append(DynamicGrammar.ConvertGrammarItem(Name, _ dr.GetString(0))) Loop 'Closing tags sb.Append("</one-of>") sb.Append("</rule>") sb.Append("</grammar>") 'Write out the new file Dim xDoc As XmlDocument = New XmlDocument xDoc.LoadXml(sb.ToString) xDoc.Save(xmlFileOutputLoc) End Sub This function builds an XML stream needed for the grammar file. It then calls the ExecuteReader function of the SqlHelper class to execute the stored procedure name passed in as a parameter. Once all the grammar items are loaded into the stream, it writes the output to disk. Note In order for the function to write the XML file, you will have to grant the ASPNET account write access to the file located at Server.MapPath(Request.ApplicationPath + "/Grammars/UserNames.grxml"). The exact file location depends on where the Chapter3 application is installed on your Web server. The username.grxml file is necessary in order for the speech engine to recognize the employee's name. Every time the speech engine processes input from the user, it returns an SML stream that indicates what was recognized. The SML contains the text interpreted by the speech engine along with a confidence score (a score of 1.0 indicates a 100 percent confidence). Controls can be configured to only accept results above a certain confidence score. I would recommend setting an initial confidence score of .70 and then adjusting it if necessary. LoadGrammarFile also calls a function named ConvertDataItem, seen as follows, which is used to format the record values as XML. Public Shared Function ConvertGrammarItem(ByVal Name As String, _ ByVal GrammarItem As String) As String ' Build the grxml <item> element Dim s As New StringBuilder s.Append("<item>") s.Append("<item>") s.Append(GrammarItem) s.Append("</item>") s.Append("<tag>$." + Name + " = """ + _ GrammarItem + """</tag>") s.Append("</item>") Return s.ToString() End Function Other Page ControlsThe sample application in this chapter utilizes a user control named common.ascx. This control is placed on every page and contains Include statements pointing to common files such as PromptGenerator.js. It also contains QA controls for our global commands Help and Repeat. Otherwise, we would have to duplicate these controls on every page. Finally, it contains a SpeechControlSettings control used to define common properties for certain types of controls. The Semantic Map control defined the semantic item associated with the user name. The value spoken by the user is stored in a session variable named UserName and can be referenced in other areas of the code. The AskUserNameConfirm QA control is used to repeat the user name to the user and ask if the value is correct. Since it is possible for the speech engine to recognize the user name incorrectly, this allows the user to start over if necessary. The confirmation control utilizes a grammar named YesNo.grxml and is an example of using the RuleRef control (see Figure 3.6). The root node for this grammar file contains references to two other rules: Yes and No. By using rule references, complex grammars can be broken down into more manageable units and then assembled into one root rule (the root rule is the one loaded by default). The rule can be referred to with the pound sign and rule name after the grammar file name. For example, the src property for the grammar tag would contain the value Grammars/Common/YesNo.grxml#YesNoRule. Figure 3.6. Partial screenshot of the Grammar Editor as it displays the YesNoRule. This rule utilizes a RuleRef control to reference the Yes and No rules. The rule name is preceded with a pound sign.
The final control on the Login page is the PasscodeQA. This control is responsible for prompting the user for the password code and then recognizing the digits entered on the phone keypad. DTMF stands for Dual Tone Multi-Frequency tones, and it represents tones resulting from pressing a keypad. The PasscodeQA control references both the Passcode.grxml and PasscodeSpoken.grxml files. The Passcode.grxml file is of mode DTMF. This is specified in the Mode property for the QA control. The PasscodeSpoken.grxml file is used for spoken digits, such as "one" or "two." The application allows the user the choice of speaking the password code or keying it in via the phone pad. This helps to ensure the security of the password code when the user is in a public area. The root rule PasscodeRule (see Figure 3.7) for both grammars references a rule named digit. Since a passcode should be exactly six digits long, we will set the Max Repeat and Min Repeat properties with a value of 6. Once six digits are entered, the speech engine interprets the result and writes out the SML. Figure 3.7. Screenshot of the Grammar Editor as it displays part of the PasscodeRule. This is the root rule used to evaluate the user's spoken passcode. The rule will collect six digits since the Max Repeat and Min Repeat properties are both set with a value of 6.Posting Back to the ServerOnce the last control is evaluated, a postback to the server will occur automatically, and code in the page_load (as follows) will call the VerifyPasscode function. Dim _DynamicGrammar As CDynamicGrammar Dim _Login As CLogin Try If Page.IsPostBack Then If siPasscode.Text <> "" Then Dim apasscode As String() = siPasscode.Text.Split(" ") Dim spasscode As String = ConvertNumtoDigits(apasscode) Dim ipasscode As Integer = Convert.ToInt32(spasscode) _Login = New CLogin If _Login.VerifyPasscode(ipasscode, siUserName.Text) Then 'Save this for later Session("UserName") = siUserName.Text Server.Transfer("MainMenu.aspx") Else 'Reset the state b/c we are going to 'prompt the user for the passcode again siPasscode.State = "Empty" End If End If Else Dim strPath As String = _ Server.MapPath(Request.ApplicationPath _ + "/Grammars/Login/UserNames.grxml") _DynamicGrammar.LoadGrammarFile(strPath, _ "UserName", "GetUserNames") End If Catch ex As Exception ExceptionManager.Publish(ex) Server.Transfer("Error.aspx") Finally _Login = Nothing _DynamicGrammar = Nothing End Try The VerifyPasscode function accepts the values stored in the semantic items siUserName and siPasscode. It then executes the VerifyLogin stored procedure to determine whether the user name matches the password code in the database. If it does, the user is redirected to the MainMenu.aspx page. If an error is encountered, the error handler will log the exception to the server's application event log. The application will then redirect the user to the Error.aspx page. The Error.aspx page contains a QA control and a DisconnectCall control. The QA control is responsible for informing the user that an error has occurred and it is necessary to contact the Main Office for further assistance. The message will play only once and then the user will be disconnected by the DisconnectCall control. Tip This chapter utilizes the Exception Management Application block available from the Microsoft Patterns and Practices Group. By default, the exception manager will log all exceptions to the server's application event log. Readers interested in learning more about the different options available for exception handling should refer to the Microsoft Web site at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/emab-rm.asp
The Main MenuThe MainMenu.aspx page is used to determine what function the user wishes to perform and then direct the user to the appropriate page. Only one QA control is needed for the MainMenu.aspx page. It contains a reference to the Common user control and a Semantic Map control. The semantic map holds one item: siMainFunction. The QA control for the page is named MainMenuQA, and the prompt function it uses is MainMenuQA_prompt. The Function to Get Product InfoFunction 1, Get Product Info, allows the user to say a product name and retrieve information about the product. The information that can be retrieved includes the number of items in stock, the number of items on order, and the unit price. This allows the salesperson to quickly reference the latest product information while at a customer's location. Ordering Speech ControlsSpeech controls are activated according to their order on the Web page. Another alternative is to assign a value to the SpeechIndex property for the QA control. This allows you to specify an order different from the physical order. The GetProductInfo.aspx page demonstrates the use of the SpeechIndex property. The ProductNameConfirm control physically resides on this page before the ProductNameQA control and by default would be activated first. The activation order for this page can be viewed by going to View and Speech Controls Outline (see Figure 3.8). Because the SpeechIndex property was utilized, the order indicates that the ProductNameQA control will be activated first instead. Figure 3.8. Screenshot of the Speech Controls Outline dialog box. This allows you to alter the order in which controls on a page will be evaluated.
Retrieving the Product InformationThe GetProductInfo.aspx page is similar to the Login.aspx page in that it uses a dynamic grammar. This is used to get product names that are not discontinued. Once the product name is confirmed, the user is directed to the ProductList.aspx page. The ProductList.aspx page loads with a call to the GetProductList stored procedure, which is used to retrieve product information from the SalesScheduling database. The data values returned from the stored procedure call are then appended, as follows, into a string that is returned from the GetProductList function. 'Set and value input parms Dim params(0) As SqlParameter params(0) = New SqlParameter("@prodname", SqlDbType.VarChar, 50) params(0).Value = prodname 'Use the parameters in a command Dim dr As SqlDataReader dr = SqlHelper.ExecuteReader(AppSettings("Chapter3.Connection"), _ CommandType.StoredProcedure, MethodInfo.GetCurrentMethod.Name, params) Dim sb As New StringBuilder Do While dr.Read sb.Append("Product " + prodname + " consists of ") sb.Append(Convert.ToString(dr("numitems")) + " items, has ") sb.Append(Convert.ToString(dr("unitsinstock")) + " items in stock, ") sb.Append(Convert.ToString(dr("unitsonorder"))) sb.Append(" items on order and lists for ") sb.Append(Convert.ToString(dr("unitprice"))) Loop Return sb.ToString The string returned from the GetProductList function will be used to assign a value to a semantic item used in the prompt function for the ProductInfoReview QA control. Finally, the user is asked whether they want to get another product. If the answer is yes, the user is redirected to the GetProductInfo page. Otherwise, the user is redirected to the MainMenu page. The Function to Get OpportunitiesAnother main menu option is get opportunities. By selecting this option, the salesperson is redirected to the GetOpportunities.aspx page. The first question asks whether the salesperson's area code has changed. If the reply is a yes, the salesperson is asked for the new area code. Otherwise, the GetOpportunityList stored procedure is used to return a dataset containing opportunities for the current day. The data returned from the stored procedure call is then formatted and returned in a new dataset. It is this dataset that is used to populate a DataTableNavigator application control. The DataTableNavigator control has built-in functionality that allows the user to navigate several rows of data. Navigation commands such as "Next" and "Read" are specified through control properties. This control greatly simplifies the process of reviewing data. For the GetOpportunityList function, the content results are formatted as one string which is then returned as a field in a dataset (as follows). 'Split the name into a first and last name Dim uname As String() = username.Split(" ") Dim firstname As String = uname(0).ToString Dim lastname As String = uname(1).ToString 'Set and value input parms Dim params(1) As SqlParameter params(0) = New SqlParameter("@firstname", SqlDbType.VarChar, 30) params(1) = New SqlParameter("@lastname", SqlDbType.VarChar, 30) params(0).Value = firstname params(1).Value = lastname 'Use the parameters in a command Dim dr As SqlDataReader dr = SqlHelper.ExecuteReader(AppSettings("Chapter3.Connection"), _ CommandType.StoredProcedure, MethodInfo.GetCurrentMethod.Name, params) 'Define a Data table used to hold the formatted results Dim dTable As New DataTable Dim colCompany As DataColumn = New DataColumn("company", _ System.Type.GetType("System.String")) dTable.Columns.Add(colCompany) Dim colOppString As DataColumn = New DataColumn("oppstring", _ System.Type.GetType("System.String")) dTable.Columns.Add(colOppString) Dim rowTable As DataRow Do While dr.Read rowTable = dTable.NewRow rowTable("company") = Convert.ToString(dr("company")) Dim strDesc As String If dr("appointment") = 1 Then strDesc = "Appointment starts at " + _ Convert.ToString(dr("starttime")) Else strDesc = "Opportunity starts at " + _ Convert.ToString(dr("starttime")) End If strDesc = strDesc + " and ends at " + Convert.ToString(dr("endtime")) strDesc = strDesc + " notes are " + Convert.ToString(dr("notes")) strDesc = strDesc + " and it was referred by " strDesc = strDesc + Convert.ToString(dr("referredby")) rowTable("oppstring") = strDesc dTable.Rows.Add(rowTable) Loop Dim ds As New DataSet ds.Tables.Add(dTable) dr = Nothing dTable = Nothing colCompany = Nothing colOppString = Nothing Return ds
Navigating the ApplicationAt any point after login, the salesperson has the option of returning to the main menu page by saying "Main Menu" or ending the call by saying "Disconnect." These commands are available through the common.ascx file. The Common user control contains four global commands that can be called from any page: Help, Repeat, Main Menu, and Disconnect. All four controls are contained within a panel Web control to identify their scope. Since the Speech Engine will read the grammars for these controls every time a page is activated, you want to limit the number of global commands. The MainMenuCmd and DisconnectCmd are different from the other global commands because they execute a triggered event when activated. The triggered events call server routines that redirect to either MainMenu.aspx or Disconnect.aspx. Private Sub DisconnectCmd_Triggered(ByVal sender As System.Object, _ ByVal e As Microsoft.Speech.Web.UI.CommandTriggeredEventArgs) _ Handles DisconnectCmd.Triggered Server.Transfer("Disconnect.aspx") End Sub Private Sub MainMenuCmd_Triggered(ByVal sender As System.Object, _ ByVal e As Microsoft.Speech.Web.UI.CommandTriggeredEventArgs) _ Handles MainMenuCmd.Triggered If Session("UserName") <> "" Then Server.Transfer("MainMenu.aspx") End If End Sub The triggered event for the Main Menu command ascertains whether the public variable that stores the user name contains a value. If the variable contains a blank value, then we know the user has not been validated and thus should not be directed to the main menu page. The session variable UserName is set in the code of Login.aspx only after the user has been successfully validated. |
< Day Day Up > |