@@ -9,9 +9,6 @@ ms.date: 10/14/2019
9
9
10
10
Use C# to build a chat bot integrated with language understanding (LUIS). The bot is built with the Azure [ Web app bot] ( https://docs.microsoft.com/azure/bot-service/ ) resource and [ Bot Framework version] ( https://github.com/Microsoft/botbuilder-dotnet ) V4.
11
11
12
- [ !INCLUDE [ Waiting for Bot refresh] ( ./includes/wait-bot-upgrade.md )]
13
-
14
-
15
12
** In this tutorial, you learn how to:**
16
13
17
14
> [ !div class="checklist"]
@@ -58,7 +55,8 @@ Use C# to build a chat bot integrated with language understanding (LUIS). The bo
58
55
59
56
1 . Select ** Create** . This creates and deploys the bot service to Azure. Part of this process creates a LUIS app named ` luis-csharp-bot-XXXX ` . This name is based on the /Azure Bot Service app name.
60
57
61
- [ ![ Create web app bot] ( ./media/bfv4-csharp/create-web-app-service.png )] ( ./media/bfv4-csharp/create-web-app-service.png#lightbox )
58
+ > [ !div class="mx-imgBorder"]
59
+ > [ ![ Create web app bot] ( ./media/bfv4-csharp/create-web-app-service.png )] ( ./media/bfv4-csharp/create-web-app-service.png#lightbox )
62
60
63
61
Wait until the bot service is created before continuing.
64
62
@@ -144,66 +142,157 @@ In order to develop the web app bot code, download the code and use on your loca
144
142
1 . Open ** Dialogs -> MainDialog .cs ** captures the utterance and sends it to the executeLuisQuery in the actStep method .
145
143
146
144
```csharp
147
- public class MainDialog : ComponentDialog
148
- {
149
- private readonly FlightBookingRecognizer _luisRecognizer ;
145
+ // Copyright (c) Microsoft Corporation. All rights reserved.
146
+ // Licensed under the MIT License.
150
147
151
- ...
148
+ using System ;
149
+ using System .Collections .Generic ;
150
+ using System .Linq ;
151
+ using System .Threading ;
152
+ using System .Threading .Tasks ;
153
+ using Microsoft .Bot .Builder ;
154
+ using Microsoft .Bot .Builder .Dialogs ;
155
+ using Microsoft .Bot .Schema ;
156
+ using Microsoft .Extensions .Logging ;
157
+ using Microsoft .Recognizers .Text .DataTypes .TimexExpression ;
152
158
153
- public MainDialog (FlightBookingRecognizer luisRecognizer , BookingDialog bookingDialog , ILogger <MainDialog > logger )
154
- : base (nameof (MainDialog ))
159
+ namespace Microsoft .BotBuilderSamples .Dialogs
160
+ {
161
+ public class MainDialog : ComponentDialog
155
162
{
156
- _luisRecognizer = luisRecognizer ;
157
- .. .
158
- }
163
+ private readonly FlightBookingRecognizer _luisRecognizer ;
164
+ protected readonly ILogger Logger ;
159
165
160
- private async Task < DialogTurnResult > ActStepAsync ( WaterfallStepContext stepContext , CancellationToken cancellationToken )
161
- {
162
- if ( ! _luisRecognizer . IsConfigured )
166
+ // Dependency injection uses this constructor to instantiate MainDialog
167
+ public MainDialog ( FlightBookingRecognizer luisRecognizer , BookingDialog bookingDialog , ILogger < MainDialog > logger )
168
+ : base ( nameof ( MainDialog ) )
163
169
{
164
- // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance.
165
- return await stepContext .BeginDialogAsync (nameof (BookingDialog ), new BookingDetails (), cancellationToken );
170
+ _luisRecognizer = luisRecognizer ;
171
+ Logger = logger ;
172
+
173
+ AddDialog (new TextPrompt (nameof (TextPrompt )));
174
+ AddDialog (bookingDialog );
175
+ AddDialog (new WaterfallDialog (nameof (WaterfallDialog ), new WaterfallStep []
176
+ {
177
+ IntroStepAsync ,
178
+ ActStepAsync ,
179
+ FinalStepAsync ,
180
+ }));
181
+
182
+ // The initial child Dialog to run.
183
+ InitialDialogId = nameof (WaterfallDialog );
166
184
}
167
185
168
- // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
169
- var luisResult = await _luisRecognizer .RecognizeAsync <FlightBooking >(stepContext .Context , cancellationToken );
170
- switch (luisResult .TopIntent ().intent )
186
+ private async Task <DialogTurnResult > IntroStepAsync (WaterfallStepContext stepContext , CancellationToken cancellationToken )
171
187
{
172
- case FlightBooking .Intent .BookFlight :
173
- await ShowWarningForUnsupportedCities (stepContext .Context , luisResult , cancellationToken );
174
-
175
- // Initialize BookingDetails with any entities we may have found in the response.
176
- var bookingDetails = new BookingDetails ()
177
- {
178
- // Get destination and origin from the composite entities arrays.
179
- Destination = luisResult .ToEntities .Airport ,
180
- Origin = luisResult .FromEntities .Airport ,
181
- TravelDate = luisResult .TravelDate ,
182
- };
183
-
184
- // Run the BookingDialog giving it whatever details we have from the LUIS call, it will fill out the remainder.
185
- return await stepContext .BeginDialogAsync (nameof (BookingDialog ), bookingDetails , cancellationToken );
186
-
187
- case FlightBooking .Intent .GetWeather :
188
- // We haven't implemented the GetWeatherDialog so we just display a TODO message.
189
- var getWeatherMessageText = " TODO: get weather flow here" ;
190
- var getWeatherMessage = MessageFactory .Text (getWeatherMessageText , getWeatherMessageText , InputHints .IgnoringInput );
191
- await stepContext .Context .SendActivityAsync (getWeatherMessage , cancellationToken );
192
- break ;
193
-
194
- default :
195
- // Catch all for unhandled intents
196
- var didntUnderstandMessageText = $" Sorry, I didn't get that. Please try asking in a different way (intent was {luisResult .TopIntent ().intent })" ;
197
- var didntUnderstandMessage = MessageFactory .Text (didntUnderstandMessageText , didntUnderstandMessageText , InputHints .IgnoringInput );
198
- await stepContext .Context .SendActivityAsync (didntUnderstandMessage , cancellationToken );
199
- break ;
188
+ if (! _luisRecognizer .IsConfigured )
189
+ {
190
+ await stepContext .Context .SendActivityAsync (
191
+ MessageFactory .Text (" NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' to the appsettings.json file." , inputHint : InputHints .IgnoringInput ), cancellationToken );
192
+
193
+ return await stepContext .NextAsync (null , cancellationToken );
194
+ }
195
+
196
+ // Use the text provided in FinalStepAsync or the default if it is the first time.
197
+ var messageText = stepContext .Options ? .ToString () ?? " What can I help you with today?\n Say something like \" Book a flight from Paris to Berlin on March 22, 2020\" " ;
198
+ var promptMessage = MessageFactory .Text (messageText , messageText , InputHints .ExpectingInput );
199
+ return await stepContext .PromptAsync (nameof (TextPrompt ), new PromptOptions { Prompt = promptMessage }, cancellationToken );
200
200
}
201
201
202
- return await stepContext .NextAsync (null , cancellationToken );
203
- }
202
+ private async Task <DialogTurnResult > ActStepAsync (WaterfallStepContext stepContext , CancellationToken cancellationToken )
203
+ {
204
+ if (! _luisRecognizer .IsConfigured )
205
+ {
206
+ // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance.
207
+ return await stepContext .BeginDialogAsync (nameof (BookingDialog ), new BookingDetails (), cancellationToken );
208
+ }
209
+
210
+ // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
211
+ var luisResult = await _luisRecognizer .RecognizeAsync <FlightBooking >(stepContext .Context , cancellationToken );
212
+ switch (luisResult .TopIntent ().intent )
213
+ {
214
+ case FlightBooking .Intent .BookFlight :
215
+ await ShowWarningForUnsupportedCities (stepContext .Context , luisResult , cancellationToken );
216
+
217
+ // Initialize BookingDetails with any entities we may have found in the response.
218
+ var bookingDetails = new BookingDetails ()
219
+ {
220
+ // Get destination and origin from the composite entities arrays.
221
+ Destination = luisResult .ToEntities .Airport ,
222
+ Origin = luisResult .FromEntities .Airport ,
223
+ TravelDate = luisResult .TravelDate ,
224
+ };
225
+
226
+ // Run the BookingDialog giving it whatever details we have from the LUIS call, it will fill out the remainder.
227
+ return await stepContext .BeginDialogAsync (nameof (BookingDialog ), bookingDetails , cancellationToken );
228
+
229
+ case FlightBooking .Intent .GetWeather :
230
+ // We haven't implemented the GetWeatherDialog so we just display a TODO message.
231
+ var getWeatherMessageText = " TODO: get weather flow here" ;
232
+ var getWeatherMessage = MessageFactory .Text (getWeatherMessageText , getWeatherMessageText , InputHints .IgnoringInput );
233
+ await stepContext .Context .SendActivityAsync (getWeatherMessage , cancellationToken );
234
+ break ;
235
+
236
+ default :
237
+ // Catch all for unhandled intents
238
+ var didntUnderstandMessageText = $" Sorry, I didn't get that. Please try asking in a different way (intent was {luisResult .TopIntent ().intent })" ;
239
+ var didntUnderstandMessage = MessageFactory .Text (didntUnderstandMessageText , didntUnderstandMessageText , InputHints .IgnoringInput );
240
+ await stepContext .Context .SendActivityAsync (didntUnderstandMessage , cancellationToken );
241
+ break ;
242
+ }
243
+
244
+ return await stepContext .NextAsync (null , cancellationToken );
245
+ }
246
+
247
+ // Shows a warning if the requested From or To cities are recognized as entities but they are not in the Airport entity list.
248
+ // In some cases LUIS will recognize the From and To composite entities as a valid cities but the From and To Airport values
249
+ // will be empty if those entity values can't be mapped to a canonical item in the Airport.
250
+ private static async Task ShowWarningForUnsupportedCities (ITurnContext context , FlightBooking luisResult , CancellationToken cancellationToken )
251
+ {
252
+ var unsupportedCities = new List <string >();
253
+
254
+ var fromEntities = luisResult .FromEntities ;
255
+ if (! string .IsNullOrEmpty (fromEntities .From ) && string .IsNullOrEmpty (fromEntities .Airport ))
256
+ {
257
+ unsupportedCities .Add (fromEntities .From );
258
+ }
259
+
260
+ var toEntities = luisResult .ToEntities ;
261
+ if (! string .IsNullOrEmpty (toEntities .To ) && string .IsNullOrEmpty (toEntities .Airport ))
262
+ {
263
+ unsupportedCities .Add (toEntities .To );
264
+ }
204
265
205
- ...
266
+ if (unsupportedCities .Any ())
267
+ {
268
+ var messageText = $" Sorry but the following airports are not supported: {string .Join (',' , unsupportedCities )}" ;
269
+ var message = MessageFactory .Text (messageText , messageText , InputHints .IgnoringInput );
270
+ await context .SendActivityAsync (message , cancellationToken );
271
+ }
272
+ }
273
+
274
+ private async Task <DialogTurnResult > FinalStepAsync (WaterfallStepContext stepContext , CancellationToken cancellationToken )
275
+ {
276
+ // If the child dialog ("BookingDialog") was cancelled, the user failed to confirm or if the intent wasn't BookFlight
277
+ // the Result here will be null.
278
+ if (stepContext .Result is BookingDetails result )
279
+ {
280
+ // Now we have all the booking details call the booking service.
206
281
282
+ // If the call to the booking service was successful tell the user.
283
+
284
+ var timeProperty = new TimexProperty (result .TravelDate );
285
+ var travelDateMsg = timeProperty .ToNaturalLanguage (DateTime .Now );
286
+ var messageText = $" I have you booked to {result .Destination } from {result .Origin } on {travelDateMsg }" ;
287
+ var message = MessageFactory .Text (messageText , messageText , InputHints .IgnoringInput );
288
+ await stepContext .Context .SendActivityAsync (message , cancellationToken );
289
+ }
290
+
291
+ // Restart the main dialog with a different message the second time around
292
+ var promptMessage = " What else can I do for you?" ;
293
+ return await stepContext .ReplaceDialogAsync (InitialDialogId , promptMessage , cancellationToken );
294
+ }
295
+ }
207
296
}
208
297
```
209
298
@@ -220,7 +309,7 @@ In Visual Studio 2019, start the bot. A browser window opens with the web app bo
220
309
1 . Enter the ** Microsoft App ID ** and ** Microsoft App password ** , found in the ** appsettings .json ** file in the root of the bot code you downloaded .
221
310
222
311
223
- 1 . In the bot emulator , enter `Book a flight from Seattle to Berlin tomorrow ` and get the same response for the basic bot as you received in the **Test in Web Chat**.
312
+ 1 . In the bot emulator , enter `Book a flight from Seattle to Berlin tomorrow ` and get the same response for the basic bot as you received in the **Test in Web Chat** in a previous section .
224
313
225
314
[](./ media / bfv4 - nodejs / ask - bot - emulator - a - question - and - get - response .png #lightbox )
226
315
0 commit comments