├── Workshop ├── README.md └── Images │ ├── Dot.png │ ├── CloneGitHubRepo.png │ ├── AppServiceSelection.png │ ├── BotDevelopmentPhases.png │ ├── PublishingSummaryPage.png │ ├── VSPublishUserInterface.png │ ├── BotLifeCycle Horizontal.png │ ├── PublishingTargetUserInterface.png │ ├── ConfigurationSettingUserInterface.png │ └── bot_framework_accelerators_header.png ├── MultiturnQnAMaker ├── README.md ├── Changes to Startup.cs.pdf ├── Dialogs │ └── Changes to MainDialog.cs.pdf ├── Services │ ├── Changes to BotServices.cs.pdf │ └── MultiturnQnAMaker.cs ├── Models │ ├── MultiturnQnAContext.cs │ ├── MultiturnQnAResultList.cs │ ├── MultiturnQnAMetadata.cs │ ├── MultiturnQnAPrompts.cs │ ├── MultiturnQnAState.cs │ └── MultiturnQnAResult.cs └── Helpers │ └── CardHelper.cs ├── DialogAccelerator ├── Images │ ├── DialogDI.png │ ├── Startup.png │ ├── BeginDialog.png │ ├── RawButton.png │ ├── PlanBBeginDialog.png │ └── AddSrcControlInVS.png ├── README.md └── GetLibraryCard.cs ├── SecuredWebChatControl ├── README.md ├── Controllers │ └── TokenController.cs └── wwwroot │ └── default.htm ├── LICENSE ├── README.md ├── .gitattributes ├── RazorWebChatHost ├── README.md └── Pages │ ├── Index.cshtml.cs │ └── Index.cshtml └── .gitignore /Workshop/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/README.md -------------------------------------------------------------------------------- /Workshop/Images/Dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/Dot.png -------------------------------------------------------------------------------- /MultiturnQnAMaker/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/MultiturnQnAMaker/README.md -------------------------------------------------------------------------------- /Workshop/Images/CloneGitHubRepo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/CloneGitHubRepo.png -------------------------------------------------------------------------------- /DialogAccelerator/Images/DialogDI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/DialogAccelerator/Images/DialogDI.png -------------------------------------------------------------------------------- /DialogAccelerator/Images/Startup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/DialogAccelerator/Images/Startup.png -------------------------------------------------------------------------------- /DialogAccelerator/Images/BeginDialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/DialogAccelerator/Images/BeginDialog.png -------------------------------------------------------------------------------- /DialogAccelerator/Images/RawButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/DialogAccelerator/Images/RawButton.png -------------------------------------------------------------------------------- /Workshop/Images/AppServiceSelection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/AppServiceSelection.png -------------------------------------------------------------------------------- /Workshop/Images/BotDevelopmentPhases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/BotDevelopmentPhases.png -------------------------------------------------------------------------------- /Workshop/Images/PublishingSummaryPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/PublishingSummaryPage.png -------------------------------------------------------------------------------- /Workshop/Images/VSPublishUserInterface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/VSPublishUserInterface.png -------------------------------------------------------------------------------- /DialogAccelerator/Images/PlanBBeginDialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/DialogAccelerator/Images/PlanBBeginDialog.png -------------------------------------------------------------------------------- /MultiturnQnAMaker/Changes to Startup.cs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/MultiturnQnAMaker/Changes to Startup.cs.pdf -------------------------------------------------------------------------------- /Workshop/Images/BotLifeCycle Horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/BotLifeCycle Horizontal.png -------------------------------------------------------------------------------- /DialogAccelerator/Images/AddSrcControlInVS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/DialogAccelerator/Images/AddSrcControlInVS.png -------------------------------------------------------------------------------- /Workshop/Images/PublishingTargetUserInterface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/PublishingTargetUserInterface.png -------------------------------------------------------------------------------- /MultiturnQnAMaker/Dialogs/Changes to MainDialog.cs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/MultiturnQnAMaker/Dialogs/Changes to MainDialog.cs.pdf -------------------------------------------------------------------------------- /Workshop/Images/ConfigurationSettingUserInterface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/ConfigurationSettingUserInterface.png -------------------------------------------------------------------------------- /Workshop/Images/bot_framework_accelerators_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/Workshop/Images/bot_framework_accelerators_header.png -------------------------------------------------------------------------------- /MultiturnQnAMaker/Services/Changes to BotServices.cs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcoder/BotFrameworkAccelerators/HEAD/MultiturnQnAMaker/Services/Changes to BotServices.cs.pdf -------------------------------------------------------------------------------- /MultiturnQnAMaker/Models/MultiturnQnAContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace QnA.MultiturnQnAMaker 5 | { 6 | public class MultiturnQnAContext 7 | { 8 | public MultiturnQnAPrompts[] Prompts { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MultiturnQnAMaker/Models/MultiturnQnAResultList.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace QnA.MultiturnQnAMaker 5 | { 6 | public class MultiturnQnAResultList 7 | { 8 | public MultiturnQnAResult[] Answers { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MultiturnQnAMaker/Models/MultiturnQnAMetadata.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace QnA.MultiturnQnAMaker 5 | { 6 | public class MultiturnQnAMetadata 7 | { 8 | public string Name { get; set; } 9 | 10 | public string Value { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MultiturnQnAMaker/Models/MultiturnQnAPrompts.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace QnA.MultiturnQnAMaker 5 | { 6 | public class MultiturnQnAPrompts 7 | { 8 | public int DisplayOrder { get; set; } 9 | 10 | public int QnaId { get; set; } 11 | 12 | public string DisplayText { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MultiturnQnAMaker/Models/MultiturnQnAState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace QnA.MultiturnQnAMaker 7 | { 8 | /// 9 | /// This is passed as the context in the body of the call to generateanswer API in the QnA service 10 | /// 11 | public class MultiturnQnAState 12 | { 13 | public int PreviousQnaId { get; set; } 14 | 15 | public string PreviousUserQuery { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MultiturnQnAMaker/Models/MultiturnQnAResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace QnA.MultiturnQnAMaker 5 | { 6 | public class MultiturnQnAResult 7 | { 8 | public string[] Questions { get; set; } 9 | 10 | public string Answer { get; set; } 11 | 12 | public double Score { get; set; } 13 | 14 | public int Id { get; set; } 15 | 16 | public string Source { get; set; } 17 | 18 | public MultiturnQnAMetadata[] Metadata { get; } 19 | 20 | public MultiturnQnAContext Context { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SecuredWebChatControl/README.md: -------------------------------------------------------------------------------- 1 | # Secured Web Chat Control 2 | This accelerator shows how to embed a Web Chat control on a web page without exposing the bot secret. You can also use it 3 | to create a quick test page so others can explore or test your bot without having to use bot emulator or browse to the 4 | portal. 5 | 6 | For more information about securely embedding a Web Chat conrol, see the Direct Line documentation 7 | [here](https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication?view=azure-bot-service-4.0) 8 | 9 | ## To Use 10 | 1. Copy wwwroot\default.htm to the wwwroot folder and replace the existing default.htm file 11 | 2. Copy Controllers\TokenController.cs to your Controllers folder in your Virtual Assistant and follow the instructions at the top 12 | of that file 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Russ Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bot Framework Development Accelerators 2 | This repository is home to a set of development accelerators for bots and advanced conversational experiences that are built 3 | on the [Microsoft Bot Framework](https://dev.botframework.com/) and the [Virtual Assistant](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-virtual-assistant-introduction?view=azure-bot-service-4.0). 4 | 5 | This repository is NOT an official Microsoft repository nor is it part of the Micorosoft Bot Framework but you should find 6 | it useful. 7 | 8 | ## [Bot Accelerator Workshop](Workshop) 9 | This workshop is a step-by-step guide for building your first enterprise bot. Its part recipe, part master plan, which you can 10 | use to accelerate your bot journey from conception to production deployment. 11 | 12 | ## [Secure Your Web Chat Control](SecuredWebChatControl) 13 | Shows how to embed a Web Chat control on a web page without exposing the bot secret 14 | 15 | ## [Add Multiturn QnA to a Virtual Assistant](MultiturnQnAMaker) 16 | Shows how to enable QnA Maker follow-on prompts in a bot that was created in Visual Studio 17 | using the Virtual Assistant template. 18 | 19 | ## [Dialog Accelerator](DialogAccelerator) 20 | Dialog template you can use to create moderate to complex conversational bot dialogs 21 | -------------------------------------------------------------------------------- /MultiturnQnAMaker/Helpers/CardHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Microsoft.Bot.Schema; 7 | using QnA.MultiturnQnAMaker; 8 | 9 | namespace QnAPrompting.Helpers 10 | { 11 | public class CardHelper 12 | { 13 | /// 14 | /// Get Hero card 15 | /// 16 | /// Title of the card 17 | /// List of suggested prompts 18 | /// Message activity 19 | public static Activity GetHeroCard(string cardTitle, QnAPrompts[] prompts) 20 | { 21 | var chatActivity = Activity.CreateMessageActivity(); 22 | var buttons = new List(); 23 | 24 | var sortedPrompts = prompts.OrderBy(r => r.DisplayOrder); 25 | foreach (var prompt in sortedPrompts) 26 | { 27 | buttons.Add( 28 | new CardAction() 29 | { 30 | Value = prompt.DisplayText, 31 | Type = ActionTypes.ImBack, 32 | Title = prompt.DisplayText, 33 | }); 34 | } 35 | 36 | var plCard = new HeroCard() 37 | { 38 | Title = cardTitle, 39 | Subtitle = string.Empty, 40 | Buttons = buttons 41 | }; 42 | 43 | var attachment = plCard.ToAttachment(); 44 | 45 | chatActivity.Attachments.Add(attachment); 46 | 47 | return (Activity)chatActivity; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /RazorWebChatHost/README.md: -------------------------------------------------------------------------------- 1 | # Razor Page Web Chat Host 2 | 3 | This Razor page shows how to securly hosts a Web Chat control and it can be used as a convenient 4 | test page during development that others can use to access your bot. To use this page, do the 5 | following: 6 | 7 | 1) Add the `Pages` folder and its contents to your bot project (could be added to your assistant or skill or both) 8 | 2) Add the following member to the Startup class in startup.cs: 9 | ```c# 10 | public static HttpClient HttpClient = new HttpClient(); 11 | ``` 12 | 3) Add a using statement to Index.cshtml.cs that resolves the Startup.HttpClient reference 13 | ``` 14 | using ; 15 | ``` 16 | 4) Add the following to ConfigureServices() method in startup.cs: 17 | ```c# 18 | // Add Razor Pages 19 | services.AddRazorPages(); 20 | ``` 21 | 5) Replace the UseEndpoints statement in Configure() method in startup.cs: 22 | ```c# 23 | .UseEndpoints(endpoints => 24 | { 25 | endpoints.MapControllers(); 26 | endpoints.MapRazorPages(); 27 | }); 28 | ``` 29 | 6) Add the Direct Line Channel to your bot in the Azure portal and use it's bot secret to set 30 | the BotSecret configuration setting in your appsettings.json file 31 | ```json 32 | "BotSecret": "", 33 | ``` 34 | 7) Take a look at the IndexModel() constructor in the code behind to see which other settings 35 | can be configured, if desired 36 | 8) Optional - Delete the `default.htm` file from the `wwwroot` folder since you now have a new home page 37 | 38 | **Note:** Unlike other approaches, this Razor page DOES NOT require a token controller API to be 39 | added to your solution since that functionality is taken care of in the code behind for this page. 40 | 41 | **Note:** Its important to understand that when this page runs in localhost it will be interacting with 42 | the deployed bot, **NOT THE BOT RUNNING IN LOCAL HOST**. So you can't use it for localhost debugging 43 | to hit breakpoints in your code unless you create a separate Bot Channels Registration that points to 44 | an ngrok-exposed localhost. 45 | 46 | If you want to use the this page to drive localhost debugging via a separate Bot Channel Registration, 47 | install ngrok and run this ngrok command 48 | ``` 49 | ngrok http 3978 -host-header=localhost:3978 50 | ``` 51 | When creating the Bot Channel Registration, you'll have to set the messaging endpoint of the Bot Channel 52 | Registration to the https version of the endpoints exposed by ngrok which should look something like 53 | the following. Remember, the ngrok exposed endpoint only lasts as long as the command window is 54 | running so the next time you run ngrok you'll have to reset/update the messaging endpoint in the Bot 55 | Chanel Registration. 56 | 57 | ``` 58 | https://.ngrok.io/api/messages 59 | ``` 60 | 61 | Details regarding securing web chat control can be found [here](https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication?view=azure-bot-service-4.0) 62 | 63 | -------------------------------------------------------------------------------- /RazorWebChatHost/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.Extensions.Configuration; 11 | using Newtonsoft.Json; 12 | using SBA_Assistant; 13 | 14 | namespace SecuredWebChat.Razor.Pages 15 | { 16 | public class IndexModel : PageModel 17 | { 18 | const string TokenGenerationUrl = "https://directline.botframework.com/v3/directline/tokens/generate"; 19 | 20 | public IndexModel(IConfiguration configuration) 21 | { 22 | BotSecret = configuration["BotSecret"]; 23 | HideUploadButton = bool.Parse(configuration["HideUploadButton"]); 24 | BotAvatarInitials = configuration["BotAvatarInitials"]; 25 | UserAvatarInitials = configuration["UserAvatarInitials"]; 26 | 27 | DLToken = GetTokenAsync().ConfigureAwait(false).GetAwaiter().GetResult().token; 28 | } 29 | public string DLToken { get; set; } 30 | 31 | // Dynamic values (not appsettings values) 32 | public string UserId { get; set; } = $"dl_{Guid.NewGuid()}"; 33 | public string UserName { get; set; } = "botUser"; 34 | 35 | //Index.cshtml settings 36 | public string BotSecret { get; set; } 37 | public bool HideUploadButton { get; set; } 38 | public string BotAvatarInitials { get; set; } 39 | public string UserAvatarInitials { get; set; } 40 | 41 | public void OnGet() 42 | { 43 | 44 | } 45 | 46 | public async Task GetTokenAsync() 47 | { 48 | using (var request = new HttpRequestMessage(HttpMethod.Post, TokenGenerationUrl)) 49 | { 50 | // For more information on exchanging a secret for a token see: 51 | // https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication#generate-token 52 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", BotSecret); 53 | request.Content = new StringContent(JsonConvert.SerializeObject(new { User = new { Id = UserId } }), Encoding.UTF8, "application/json"); 54 | 55 | using (var response = await Startup.HttpClient.SendAsync(request).ConfigureAwait(false)) 56 | { 57 | if (response.IsSuccessStatusCode) 58 | { 59 | var body = await response.Content.ReadAsStringAsync(); 60 | var dlToken = JsonConvert.DeserializeObject(body); 61 | dlToken.userId = UserId; 62 | return dlToken; 63 | } 64 | else 65 | { 66 | throw new Exception($"Could not swap bot secret for token - Status Code: {response.StatusCode}, Response: {response.ReasonPhrase}"); 67 | } 68 | } 69 | } 70 | 71 | return null; 72 | } 73 | 74 | public class DirectLineToken 75 | { 76 | public string userId { get; set; } 77 | public string token { get; set; } 78 | public int expires_in { get; set; } 79 | public string streamUrl { get; set; } 80 | } 81 | 82 | } 83 | } -------------------------------------------------------------------------------- /MultiturnQnAMaker/Services/MultiturnQnAMaker.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Net.Http; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Bot.Builder; 10 | using Microsoft.Bot.Builder.AI.QnA; 11 | using Microsoft.Bot.Builder.Dialogs; 12 | using Microsoft.Bot.Schema; 13 | using Microsoft.Extensions.Configuration; 14 | using Newtonsoft.Json; 15 | using QnAPrompting.Helpers; 16 | 17 | namespace QnA.MultiturnQnAMaker 18 | { 19 | public class MultiturnQnAMaker : QnAMaker 20 | { 21 | public const string FunctionStateName = "functionState"; 22 | private readonly HttpClient _httpClient; 23 | private readonly QnAMakerEndpoint _endpoint; 24 | private readonly QnAMakerOptions _options; 25 | 26 | public MultiturnQnAMaker(QnAMakerEndpoint endpoint, HttpClient httpClient, IBotTelemetryClient telemetryClient, bool logPersonalInformation = false) : base(endpoint, null, httpClient, telemetryClient, logPersonalInformation) 27 | { 28 | _httpClient = httpClient; 29 | _endpoint = endpoint; 30 | _options = new QnAMakerOptions { Top = 3 }; 31 | } 32 | 33 | public async Task<(MultiturnQnAState newState, IEnumerable output)> GetAnswersAsync(MultiturnQnAState oldState, ITurnContext turnContext, QnAMakerOptions options, Dictionary telemetryProperties, Dictionary telemetryMetrics = null) 34 | { 35 | Activity outputActivity = null; 36 | MultiturnQnAState newState = null; 37 | 38 | var query = turnContext.Activity.Text; 39 | var qnaResult = await QueryQnAServiceAsync(query, oldState); 40 | var qnaAnswer = qnaResult[0].Answer; 41 | var prompts = qnaResult[0].Context?.Prompts; 42 | 43 | if (prompts == null || prompts.Length < 1) 44 | { 45 | outputActivity = MessageFactory.Text(qnaAnswer); 46 | } 47 | else 48 | { 49 | // Set bot state only if prompts are found in QnA result 50 | newState = new MultiturnQnAState 51 | { 52 | PreviousQnaId = qnaResult[0].Id, 53 | PreviousUserQuery = query 54 | }; 55 | 56 | outputActivity = CardHelper.GetHeroCard(qnaAnswer, prompts); 57 | } 58 | 59 | return (newState, new Activity[] { outputActivity }); 60 | } 61 | 62 | public async Task QueryQnAServiceAsync(string query, MultiturnQnAState oldState) 63 | { 64 | var requestUrl = $"{_endpoint.Host}/knowledgebases/{_endpoint.KnowledgeBaseId}/generateanswer"; 65 | var request = new HttpRequestMessage(HttpMethod.Post, requestUrl); 66 | var jsonRequest = JsonConvert.SerializeObject( 67 | new 68 | { 69 | question = query, 70 | top = _options.Top, 71 | context = oldState, 72 | strictFilters = _options.StrictFilters, 73 | metadataBoost = _options.MetadataBoost, 74 | scoreThreshold = _options.ScoreThreshold, 75 | }, Formatting.None); 76 | 77 | request.Headers.Add("Authorization", $"EndpointKey {_endpoint.EndpointKey}"); 78 | request.Content = new StringContent(jsonRequest, System.Text.Encoding.UTF8, "application/json"); 79 | 80 | var response = await _httpClient.SendAsync(request); 81 | response.EnsureSuccessStatusCode(); 82 | 83 | var contentString = await response.Content.ReadAsStringAsync(); 84 | 85 | var result = JsonConvert.DeserializeObject(contentString); 86 | 87 | return result.Answers; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /SecuredWebChatControl/Controllers/TokenController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.IdentityModel.Protocols; 10 | using Newtonsoft.Json; 11 | using Newtonsoft.Json.Linq; 12 | 13 | /* ---------------------------------------------------------------------------------- 14 | * ToDo: Important tasks that must be complete for TokenController will work 15 | * The following tasks must be completed before running this code or it will fail: 16 | * 1) To avoid connection exhaustion issues, this code uses a single static instance 17 | * of an HttpClient that needs to be added as a member of the Startup class: 18 | * `public static HttpClient HttpClient = new HttpClient();` 19 | * 2) Change the fully qualified namespace of the reference to the HttpClient on line 42 20 | * of the TokenController constructor to match the namespace of your Startup class: 21 | * `Client = .Startup.HttpClient;` 22 | * 2) This code expects that to get the DirectLine secret from the appsettings.json file so 23 | * you'll need to add the following setting to the appsettings.json file: 24 | * `"directLineSecret": "",` 25 | * 3) Go to the Azure portal and enable the Direct Line channel and copy the Secret Key 26 | * and paste it in as the value of the directLineSecret setting that you created in 27 | * the previous step. 28 | * ---------------------------------------------------------------------------------- 29 | */ 30 | namespace SecuredWebChatConrol.Controllers 31 | { 32 | [Route("api/[controller]")] 33 | [ApiController] 34 | public class TokenController : ControllerBase 35 | { 36 | public IConfiguration Configuration { get; set; } 37 | public HttpClient Client { get; set; } 38 | 39 | public TokenController(IConfiguration configuration) 40 | { 41 | // If you are seeing a runtime error that complains about not being able to inject the 42 | // HttpClient, then see the note at the note at the top of this file 43 | Configuration = configuration; 44 | Client = Microsoft.BotBuilderSamples.Startup.HttpClient; 45 | } 46 | 47 | // GET: api/Token 48 | [HttpGet] 49 | [Produces("application/json")] 50 | async public Task Get() 51 | { 52 | string generateTokenUri = "https://directline.botframework.com/v3/directline/tokens/generate"; 53 | var directLineSecret = Configuration["directLineSecret"]; 54 | 55 | return JsonConvert.SerializeObject(await GetTokenAsync(generateTokenUri, directLineSecret)); 56 | } 57 | 58 | // GET: api/Token/5 59 | [HttpGet("{token}", Name = "Get")] 60 | [Produces("application/json")] 61 | async public Task Get(string directLineSecret) 62 | { 63 | return JsonConvert.SerializeObject(await GetTokenAsync("https://directline.botframework.com/v3/directline/tokens/refresh", directLineSecret)); 64 | } 65 | 66 | async Task GetTokenAsync(string api, string directLineSecret) 67 | { 68 | dynamic response = new JObject(); 69 | 70 | // Initialize the response object 71 | response.Token = ""; 72 | response.UserId = ""; 73 | response.IsSuccess = false; 74 | 75 | // Swap DirectLine secret for access token 76 | Client.DefaultRequestHeaders.Remove("Authorization"); 77 | Client.DefaultRequestHeaders.Add("Authorization", $"Bearer {directLineSecret}"); 78 | 79 | HttpResponseMessage directLineResponse = await Client.PostAsync(api, null); 80 | 81 | response.IsSuccess = directLineResponse.IsSuccessStatusCode; 82 | 83 | if ((bool)response.IsSuccess) 84 | { 85 | string body = directLineResponse.Content.ReadAsStringAsync().Result; 86 | 87 | response.Token = (string)JObject.Parse(body)["token"]; 88 | response.UserId = $"dl_{Guid.NewGuid()}"; 89 | } 90 | 91 | return response; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /DialogAccelerator/README.md: -------------------------------------------------------------------------------- 1 | # Dialog Accelerator 2 | This accelerator shows how to use the Dialog Accelerator template to create 3 | moderate to complex conversational bot dialogs. This template lays out a 4 | working roadmap of how to code the most common interaction (conversation) 5 | scenarios: 6 | * Simple prompts 7 | * Built-in validation 8 | * Custom validation 9 | * How to skip asking users questions that already have answers 10 | * How branch conversations and call independent dialog (Good for dialog reuse or 11 | modularizing a conversation) 12 | * How to localize prompts for multilingual bots 13 | 14 | For more advanced conversational flows, browse to: 15 | * [Looping](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-dialog-manage-complex-conversation-flow?view=azure-bot-service-4.0&tabs=csharp) 16 | * [Handling interruptions](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-handle-user-interrupt?view=azure-bot-service-4.0&tabs=csharp) 17 | * [Overview of dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) 18 | 19 | ## To Use 20 | Leveraging the Dialog Accelerator is not an exact science, but rather it's more of 21 | what you might call a "*muddle*" technique that's a mix of prescribed and improvised 22 | steps. In the end though, this process is a lot faster than having to start from 23 | first principles. 24 | 25 | Incorporating this template into your solution involves two phases: 26 | * Phase 1 - Get template sample code running (this becomes your working roadmap) 27 | * Phase 2 - Repurpose template sample code for your particular scenario 28 | To make incorporating the template easier, Visual Studio **ToDo** Tasks have been 29 | added to the code to provide step-by-step instructions for accomplishing phase 1. 30 | 31 | ### Phase 1 - Get Template Code Running 32 | 33 | 1. In Visual Studio, open your Skills bot project, right-click the **Dialogs** folder 34 | and choose **Add | Class...** and name the class after the LUIS Intent you're targeting 35 | with this dialog and then choose **Add** 36 | 37 | 2. Copy the entire source code of GetLibraryCard.cs found [here](GetLibraryCard.cs) into 38 | your paste buffer by clicking the **Raw** button and then Control-A and then Control-C 39 | > 40 | 3. Switch back Visual Studio and delete the entire contents of the file you created 41 | in Step 1 and replace it with the current contents of your paste buffer (Control-V) 42 | 43 | 4. To expedite "*muddling*", Visual Studio **ToDo** tasks have been added to the code 44 | which you can see in list form if you choose **View | Task List** (or Control-W,T). 45 | Double-clicking a task will take you right were you need to be to carry out the task. 46 | 47 | When you've finished all the tasks the code should compile without errors and 48 | you'll be ready to integrate it into the `MainDialog`. 49 | 5. Dependency-inject the dialog into `MainDialog` by adding 50 | `YourIntentDialog yourIntentCard,` to the constructor of the `MainDialog` class 51 | in **MainDialog.cs** 52 | > 53 | 6. Locate the "Register dialogs" comment in the constructor and add `youIntentDialog` 54 | 55 | ```c# 56 | // Register dialogs 57 | AddDialog(sampleDialog); 58 | AddDialog(yourIntentDialog); 59 | ``` 60 | 61 | 7. While still in **MainDialog.cs**, navigate to the `RouteAsync()` method and add 62 | the following code to the `switch(intent)` statement: 63 | 64 | ```c# 65 | case .Intent.YourIntentName: 66 | { 67 | turnResult = await dc.BeginDialogAsync(nameof(YourIntentDialog)); 68 | 69 | break; 70 | } 71 | ``` 72 | 73 | > 74 | If you don't have a LUIS Intent that corresponds to this new dialog yet, you 75 | can go with "*Plan B*" and make the `case YourSkillName.Intent.Sample:` 76 | statement look like the following: 77 | > 78 | 79 | 8. Finally, register the dialog for dependency injection by opening *Startup.cs* and 80 | choosing **Edit | Find and Replace | Quick Find** (or **Ctrl-F**) and typing **Register 81 | dialogs** in the search field and hit Return. Now add 82 | `services.AddTransient();` and it should look like the following: 83 | > 84 | 85 | 9. Now your ready to test out Phase 1. Set your Skill to be the StartUp Project and 86 | start the the degugger. Now open your bot in the Bot Emulator and invoke your LUIS 87 | Intent. If you didn't have a LUIS Intent and instead used Plan "B" then type **sample 88 | dialog** to kick it off. 89 | 90 | Study the code to learn how to test out all the varios scenarios the template 91 | supports. For example, try and give it a non-integer for age to see built-in 92 | validation or an email without the '@' to see custom validation. Branching is 93 | invoked if you type "already@member" or "county@employee" (which launches the 94 | SampleDialog). 95 | 96 | ### Phase 2 - *Muddle* through to Repurpose Template for Your Particular Scenario 97 | 98 | Now that you've got the template running and you've explored all the scenarios, 99 | your ready accelerate the development of your dialog by repurposing the various 100 | steps to suit your conversational flow. But before you do that there are a few 101 | things you should do to customize the new dialog to fit your scenario 102 | * Modify the conversation state model class so it has the proper conversation 103 | state properties 104 | * (Add other customizations) 105 | 106 | The last task of phase 2 is to repurpose the steps to support the conversation 107 | flow you have modeled out for this dialog. Once you've done that it should be 108 | mission complete and you're ready to repeat this same process for the rest of 109 | the dialogs for your solution. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb -------------------------------------------------------------------------------- /SecuredWebChatControl/wwwroot/default.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | 22 | 23 | Index 24 | 25 | 26 | 27 | Question and answer bot 28 | 29 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /RazorWebChatHost/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model SecuredWebChat.Razor.Pages.IndexModel 3 | @{ 4 | Layout = null; 5 | } 6 | 7 | @using Microsoft.AspNetCore.Mvc.Rendering; 8 | 9 | @* 10 | This Razor page shows how to securly hosts a Web Chat control and it can be used as a convenient 11 | test page during development that other can use to access your bot. To use this page, do the 12 | following: 13 | 14 | 1) Copy Index.cshtml and Index.cshtml.cs to your Pages folder in your Virtual Assistant's project 15 | 2) Add the following static property to the Startup class in Startup.cs 16 | public static HttpClient HttpClient = new HttpClient(); 17 | 3) Add a using statement to Index.cshtml.cs that resolves the Startup.HttpClient reference 18 | using ; 19 | 4) Make sure your Virtual Assistant's Configure() method in Startup.cs specifies the UseEndpoints 20 | as follows: 21 | .UseEndpoints(endpoints => 22 | { 23 | endpoints.MapControllers(); 24 | endpoints.MapRazorPages(); 25 | }) 26 | 5) Add the Direct Line Channel to your bot in the Azure portal and use it's bot secret to set 27 | the BotSecret configuration setting in your appsettings.json file. 28 | 6) Take a look at the IndexModel() constructor in the code behind to see which other settings 29 | can be configured, if desired 30 | 31 | Note: Unlike other approaches, this Razor page DOES NOT require a token controller API to be 32 | added to your solution since that functionality is taken care of in the code behind for this page. 33 | 34 | For details regarding securing web chat control, see: https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication?view=azure-bot-service-4.0 35 | *@ 36 | 37 | 38 | 39 | 40 | 41 | 42 | Index 43 | 44 | 45 | 46 | Question and answer bot 47 | 48 | 60 | 61 | 62 | 63 | 64 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /DialogAccelerator/GetLibraryCard.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Bot.Builder; 5 | using Microsoft.Bot.Builder.Dialogs; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | // ToDo Phase 1 - Step 01: Replace "LibraryBotSkill" in the following lines with the name of your skill 10 | using LibraryBotSkill.Services; 11 | namespace LibraryBotSkill.Dialogs 12 | { 13 | // ToDo Phase 1 - Step 02: Right-click GetLibraryCardDialog and choose "Rename..." 14 | // and use the concatenation of the name of your Intent and the word "Dialog" as 15 | // the new name of the class. So it should look like YourIntentDialog 16 | public class GetLibraryCardDialog : ComponentDialog 17 | { 18 | // To localize a multilingual bot, use the approach outlined in the main VA Assistant's OnboardingDialog 19 | //private static OnboardingResponses _responder = new OnboardingResponses(); 20 | 21 | // ToDo Phase 1 - Step 03: Right-click MembershipState and choose "Rename..." and 22 | // give your conversation state model class it's proper name. 23 | private IStatePropertyAccessor _accessor; 24 | private MembershipState _state; 25 | 26 | public GetLibraryCardDialog( 27 | BotServices botServices, 28 | ConversationState conversationState, 29 | IBotTelemetryClient telemetryClient) 30 | : base(nameof(GetLibraryCardDialog)) 31 | { 32 | // Get the conversation state accessor so waterfall steps can use it 33 | _accessor = conversationState.CreateProperty(nameof(MembershipState)); 34 | InitialDialogId = nameof(GetLibraryCardDialog); 35 | 36 | // The steps of this WaterfallDialog show: 37 | // - Simple prompts (TextPrompt, NumberPrompt) 38 | // - Browse here to see how to code other prompt types: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-compositcontrol?view=azure-bot-service-4.0&tabs=csharp 39 | // - Built-in validation (NumberPrompt only allow integers) 40 | // - Custom validation (email and verification code) 41 | // - How to skip asking users questions that already have answers (sc.NextAsync() 42 | // - "Branch" conversation by calling nested dialog. Good for dialog reuse or modularizing a conversation (BeginDialogAsync in CheckIfCountyEmployee()) 43 | // - How to localize the prompts of a multilingual bot (commented out currently in AskForName()) 44 | // For more advanced conversation flow: 45 | // - Looping: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-dialog-manage-complex-conversation-flow?view=azure-bot-service-4.0&tabs=csharp 46 | // - Handling interruptions: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-handle-user-interrupt?view=azure-bot-service-4.0&tabs=csharp 47 | // - Overview of dialogs: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0 48 | // ToDo Phase 1 - Step 04: Right-click getLibraryCard member and choose "Rename..." and 49 | // give this WaterfallStep variable it's proper name. Typically, this will 50 | // match the name of the intent 51 | var getLibraryCard = new WaterfallStep[] 52 | { 53 | AskForName, 54 | AskForAge, 55 | AskForEmail, 56 | CheckIfAlreadyMember, 57 | CheckIfCountyEmployee, 58 | GetVerificationCode, 59 | FinishGetLibraryCardDialog, 60 | }; 61 | 62 | // To capture built-in waterfall dialog telemetry, set the telemetry client 63 | // of the component dialog and new waterfall dialog (i.e. the next 2 lines of code) 64 | TelemetryClient = telemetryClient; 65 | AddDialog(new WaterfallDialog(InitialDialogId, getLibraryCard) { TelemetryClient = telemetryClient }); 66 | AddDialog(new TextPrompt(DialogIds.NamePrompt)); 67 | AddDialog(new NumberPrompt(DialogIds.AgePrompt)); 68 | AddDialog(new TextPrompt(DialogIds.EmailPrompt, ValidateEmailAddress)); 69 | AddDialog(new TextPrompt(DialogIds.VerificationPrompt, ValidateVerificationCode)); 70 | } 71 | 72 | /// 73 | /// This step shows how a simple text prompts works 74 | /// 75 | /// 76 | /// To see sample code for other prompt types: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-compositcontrol?view=azure-bot-service-4.0&tabs=csharp 77 | /// 78 | public async Task AskForName(WaterfallStepContext sc, CancellationToken cancellationToken) 79 | { 80 | // Get converation state so we can check to see if name has already been provided 81 | _state = await _accessor.GetAsync(sc.Context, () => new MembershipState()); 82 | 83 | await sc.Context.SendActivityAsync("I can help you get a library card!"); 84 | 85 | // If first name has already been provided 86 | if (!string.IsNullOrEmpty(_state.Name)) 87 | { 88 | // Skip this step and pass first name to next step 89 | return await sc.NextAsync(_state.Name); 90 | } 91 | else 92 | { 93 | return await sc.PromptAsync(DialogIds.NamePrompt, new PromptOptions 94 | { 95 | Prompt = MessageFactory.Text("What's your name?") 96 | }); 97 | 98 | // To localize a multilingual bot, use the approach below instead of the one above 99 | //return await sc.PromptAsync(DialogIds.NamePrompt, new PromptOptions() 100 | //{ 101 | // Prompt = await _responder.RenderTemplate(sc.Context, sc.Context.Activity.Locale, OnboardingResponses.ResponseIds.NamePrompt), 102 | //}); 103 | } 104 | } 105 | 106 | /// 107 | /// This step shows how to code a numeric prompt which shows an example of 108 | /// the framework's built-in validation 109 | /// 110 | /// 111 | /// The built-in validation for a NumberPrompt will only accept an integer 112 | /// so you can enter any non-integer value to see the built-in validation in action 113 | /// 114 | public async Task AskForAge(WaterfallStepContext sc, CancellationToken cancellationToken) 115 | { 116 | _state = await _accessor.GetAsync(sc.Context, () => new MembershipState()); 117 | // Save the answer from the previous step in conversation state 118 | _state.Name = (string)sc.Result; 119 | await _accessor.SetAsync(sc.Context, _state, cancellationToken); 120 | 121 | // If age has already been provided 122 | if (_state.Age != 0) 123 | { 124 | // Skip this step and pass age to next step 125 | return await sc.NextAsync(_state.Age); 126 | } 127 | else 128 | { 129 | return await sc.PromptAsync(DialogIds.AgePrompt, new PromptOptions 130 | { 131 | Prompt = MessageFactory.Text("How old are you?"), 132 | // Override the built-in integer retry prompt with a scenario-friendly one 133 | RetryPrompt = MessageFactory.Text("Please enter age as an integer") 134 | }); 135 | } 136 | } 137 | 138 | /// 139 | /// This step shows how to provide a custom retry prompt for custom validation 140 | /// 141 | /// 142 | /// Other than providing the custom retry prompt, this step is just another 143 | /// example of a simple prompt 144 | /// 145 | public async Task AskForEmail(WaterfallStepContext sc, CancellationToken cancellationToken) 146 | { 147 | _state = await _accessor.GetAsync(sc.Context, () => new MembershipState()); 148 | // Save the answer from the previous step in conversation state 149 | _state.Age = (int)sc.Result; 150 | await _accessor.SetAsync(sc.Context, _state, cancellationToken); 151 | 152 | if (!string.IsNullOrEmpty(_state.Email)) 153 | { 154 | // Skip this step and pass email to next step 155 | return await sc.NextAsync(_state.Email); 156 | } 157 | else 158 | { 159 | return await sc.PromptAsync(DialogIds.EmailPrompt, new PromptOptions 160 | { 161 | Prompt = MessageFactory.Text("What is your email?"), 162 | // Provide retry prompt for custom email validation on a TextPrompt 163 | RetryPrompt = MessageFactory.Text("That's not a valid email address, please try again") 164 | }); 165 | } 166 | } 167 | 168 | /// 169 | /// Custom email validator logic that was assigned in the call to AddDialog() 170 | /// in the constructor 171 | /// 172 | /// 173 | /// To get the proper signature for a prompt's validation delegate, right-click 174 | /// the prompt in the AddDialog() call (TextPrompt in this case) and then select 175 | /// "Go To Definition" option and then right-click PromptValidator and 176 | /// choose "Go To Definition" and copy the delegate signature and paste it in 177 | /// here dropping the "T" in method name and changing the "T" in PromptValidatorContext 178 | /// to "string" 179 | /// 180 | async private Task ValidateEmailAddress(PromptValidatorContext promptContext, CancellationToken cancellationToken) 181 | { 182 | // Get the user's response 183 | string email = promptContext.Recognized.Value; 184 | 185 | // Simple validation rule says email is only valid if it contains an '@' 186 | return email.Contains('@'); 187 | } 188 | 189 | /// 190 | /// Silent branching step to decide what direction the conversation should go 191 | /// 192 | /// 193 | /// This step shows another example of how to manage conversation flow. 194 | /// It's also interesting in that its a step without an associatede dialog 195 | /// prompt so its sole job is deciding how to proceed given the current 196 | /// context/state of the conversation (i.e. end dialog or continue to 197 | /// next step) 198 | /// 199 | /// To test the "end dialog" branch, enter "already@member" when asked for 200 | /// email. 201 | /// 202 | public async Task CheckIfAlreadyMember(WaterfallStepContext sc, CancellationToken cancellationToken) 203 | { 204 | _state = await _accessor.GetAsync(sc.Context, () => new MembershipState()); 205 | // Save the answer from the previous step in conversation state 206 | _state.Email = (string)sc.Result; 207 | await _accessor.SetAsync(sc.Context, _state, cancellationToken); 208 | 209 | if (_state.Email == "already@member") 210 | { 211 | await sc.Context.SendActivityAsync($"Looks like you already have a library card. We'll send your membership information to {_state.Email}"); 212 | 213 | return await sc.EndDialogAsync(); 214 | } 215 | else 216 | { 217 | // We can safely move to next next step 218 | return await sc.NextAsync(); 219 | } 220 | } 221 | 222 | /// 223 | /// This step shows how to "Branch" a conversation by calling a another dialog 224 | /// 225 | /// 226 | /// To exercise this branched conversation, enter "county@employee" as the 227 | /// email address. This will begin a separate dialog (SampleDialog in this 228 | /// case) and once that dialog finishes the conversation will pick back up 229 | /// with the next step in this dialog (which is GetVerificationCode()) 230 | /// 231 | public async Task CheckIfCountyEmployee(WaterfallStepContext sc, CancellationToken cancellationToken) 232 | { 233 | if (_state.Email == "county@employee") 234 | { 235 | await sc.Context.SendActivityAsync($"I see you are county employee. I'll need to ask you a few additional questions."); 236 | 237 | // To make it easier to reuse this sample code, we'll use SampleDialog which 238 | // comes with the Virtual Assistant template code to show how to branch a 239 | // conversation and call a nested dialog 240 | return await sc.BeginDialogAsync(nameof(SampleDialog), null, cancellationToken); 241 | } 242 | 243 | return await sc.NextAsync(); 244 | } 245 | 246 | /// 247 | /// This step shows how bot can creatively tie into existing business process (email in this case) 248 | /// 249 | /// 250 | /// This step shows how a bot might integrate with an existing business process 251 | /// that is email based to verify a code that is sent to the bot user. It's 252 | /// another example of leveraging custom validation. 253 | /// 254 | public async Task GetVerificationCode(WaterfallStepContext sc, CancellationToken cancellationToken) 255 | { 256 | // Get conversation state so we can have access to email that was provided previously 257 | _state = await _accessor.GetAsync(sc.Context, () => new MembershipState()); 258 | 259 | return await sc.PromptAsync(DialogIds.VerificationPrompt, new PromptOptions 260 | { 261 | Prompt = MessageFactory.Text($"We've sent a verification email to {_state.Email}. Please enter the verification code"), 262 | // Provide retry prompt for custom verification code validation for TextPrompt 263 | RetryPrompt = MessageFactory.Text("Sorry, that is an invalid verification code. Please check code and try again.") 264 | }); 265 | } 266 | 267 | /// 268 | /// Custom verification code validator logic that was assigned in the call to AddDialog() in the constructor 269 | /// 270 | /// 271 | /// To get the proper signature for a prompt's validation delegate, right-click 272 | /// the prompt in the AddDialog() call (TextPrompt in this case) and then select 273 | /// "Go To Definition" option and then right-click PromptValidator and choose 274 | /// "Go To Definition" and copy the delegate signature and paste it in here dropping 275 | /// the "T" in method name and changing the "T" in PromptValidatorContext to "string" 276 | /// 277 | async private Task ValidateVerificationCode(PromptValidatorContext promptContext, CancellationToken cancellationToken) 278 | { 279 | string verificationCode = promptContext.Recognized.Value; 280 | 281 | return verificationCode == "verified"; 282 | } 283 | 284 | /// 285 | /// The last step sums up the conversation 286 | /// 287 | /// 288 | /// The last step in a dialog generally sums up the conversation and 289 | /// performs an action that achieves the goal of the conversation which 290 | /// in this example would be to call a back end service to create a 291 | /// library membership for the user and send them a confirmation email. 292 | /// 293 | public async Task FinishGetLibraryCardDialog(WaterfallStepContext sc, CancellationToken cancellationToken) 294 | { 295 | _state = await _accessor.GetAsync(sc.Context, () => new MembershipState()); 296 | // Save the answer from the previous step in conversation state 297 | string verificationCode = (string)sc.Result; 298 | 299 | // Provide summary of conversation for this dialog 300 | await sc.Context.SendActivityAsync($"We're all set {_state.Name}! A confirmation will all your membership details will be emailed to {_state.Email}"); 301 | 302 | // ToDo Phase 2 - Step 01: Perform goal of conversation here (call membership service in this case) 303 | // libraryService.CreateMembership(_state); 304 | 305 | return await sc.EndDialogAsync(); 306 | } 307 | 308 | // ToDo Phase 2 - Step 02: Customize DialogIds for your dialog's requirements 309 | private class DialogIds 310 | { 311 | public const string NamePrompt = "FirstNamePrompt"; 312 | public const string LastNamePrompt = "LastNamePrompt"; 313 | public const string AgePrompt = "AgePrompt"; 314 | public const string EmailPrompt = "EmailPrompt"; 315 | public const string VerificationPrompt = "VerificationPrompt"; 316 | } 317 | } 318 | 319 | // ToDo Phase 2 - Step 03: Move this class to it's own file in a "Models" solution folder 320 | // ToDo Phase 2 - Step 04: Customize conversation state class for your dialog's requirements 321 | public class MembershipState 322 | { 323 | public string Name { get; set; } 324 | public int Age { get; set; } 325 | public string Email { get; set; } 326 | } 327 | } 328 | --------------------------------------------------------------------------------