diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/AdapterWithErrorHandler.cs b/samples/csharp_dotnetcore/84.core-bot-clu/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..832aaa8bb0 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/AdapterWithErrorHandler.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples +{ + public class AdapterWithErrorHandler : CloudAdapter + { + public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger logger, ConversationState conversationState = default) + : base(auth, logger) + { + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + // NOTE: In production environment, you should consider logging this to + // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how + // to add telemetry capture to your bot. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Send a message to the user + var errorMessageText = "The bot encountered an error or bug."; + var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput); + await turnContext.SendActivityAsync(errorMessage); + + errorMessageText = "To continue to run this bot, please fix the bot source code."; + errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput); + await turnContext.SendActivityAsync(errorMessage); + + if (conversationState != null) + { + try + { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" in a Web pages. + await conversationState.DeleteAsync(turnContext); + } + catch (Exception e) + { + logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); + } + } + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/BookingDetails.cs b/samples/csharp_dotnetcore/84.core-bot-clu/BookingDetails.cs new file mode 100644 index 0000000000..c13c1a33db --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/BookingDetails.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.BotBuilderSamples +{ + public class BookingDetails + { + public string Destination { get; set; } + + public string Origin { get; set; } + + public string TravelDate { get; set; } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Bots/DialogAndWelcomeBot.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Bots/DialogAndWelcomeBot.cs new file mode 100644 index 0000000000..f40f14d8f3 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Bots/DialogAndWelcomeBot.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Microsoft.BotBuilderSamples.Bots +{ + public class DialogAndWelcomeBot : DialogBot + where T : Dialog + { + public DialogAndWelcomeBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) + : base(conversationState, userState, dialog, logger) + { + } + + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in membersAdded) + { + // Greet anyone that was not the target (recipient) of this message. + // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. + if (member.Id != turnContext.Activity.Recipient.Id) + { + var welcomeCard = CreateAdaptiveCardAttachment(); + var response = MessageFactory.Attachment(welcomeCard, ssml: "Welcome to Bot Framework!"); + await turnContext.SendActivityAsync(response, cancellationToken); + await Dialog.RunAsync(turnContext, ConversationState.CreateProperty("DialogState"), cancellationToken); + } + } + } + + // Load attachment from embedded resource. + private Attachment CreateAdaptiveCardAttachment() + { + var cardResourcePath = "CoreBotCLU.Cards.welcomeCard.json"; + + using (var stream = GetType().Assembly.GetManifestResourceStream(cardResourcePath)) + { + using (var reader = new StreamReader(stream)) + { + var adaptiveCard = reader.ReadToEnd(); + return new Attachment() + { + ContentType = "application/vnd.microsoft.card.adaptive", + Content = JsonConvert.DeserializeObject(adaptiveCard), + }; + } + } + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Bots/DialogBot.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Bots/DialogBot.cs new file mode 100644 index 0000000000..4d26cae35f --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Bots/DialogBot.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples.Bots +{ + // This IBot implementation can run any type of Dialog. The use of type parameterization is to allows multiple different bots + // to be run at different endpoints within the same project. This can be achieved by defining distinct Controller types + // each with dependency on distinct IBot types, this way ASP Dependency Injection can glue everything together without ambiguity. + // The ConversationState is used by the Dialog system. The UserState isn't, however, it might have been used in a Dialog implementation, + // and the requirement is that all BotState objects are saved at the end of a turn. + public class DialogBot : ActivityHandler + where T : Dialog + { + protected readonly Dialog Dialog; + protected readonly BotState ConversationState; + protected readonly BotState UserState; + protected readonly ILogger Logger; + + public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) + { + ConversationState = conversationState; + UserState = userState; + Dialog = dialog; + Logger = logger; + } + + public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) + { + await base.OnTurnAsync(turnContext, cancellationToken); + + // Save any state changes that might have occurred during the turn. + await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken); + await UserState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + Logger.LogInformation("Running dialog with Message Activity."); + + // Run the Dialog with the new message Activity. + await Dialog.RunAsync(turnContext, ConversationState.CreateProperty("DialogState"), cancellationToken); + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Cards/welcomeCard.json b/samples/csharp_dotnetcore/84.core-bot-clu/Cards/welcomeCard.json new file mode 100644 index 0000000000..4d4d8d399c --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Cards/welcomeCard.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "medium", + "size": "default", + "weight": "bolder", + "text": "Welcome to Bot Framework!", + "wrap": true, + "maxLines": 0 + }, + { + "type": "TextBlock", + "size": "default", + "isSubtle": true, + "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", + "wrap": true, + "maxLines": 0 + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Get an overview", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" + }, + { + "type": "Action.OpenUrl", + "title": "Ask a question", + "url": "https://stackoverflow.com/questions/tagged/botframework" + }, + { + "type": "Action.OpenUrl", + "title": "Learn how to deploy", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" + } + ] +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluApplication.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluApplication.cs new file mode 100644 index 0000000000..4673ebe827 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluApplication.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.BotBuilderSamples.Clu +{ + /// + /// Data describing a CLU application. + /// + public class CluApplication + { + /// + /// Initializes a new instance of the class. + /// + /// CLU project name. + /// CLU model deployment name. + /// CLU subscription or endpoint key. + /// CLU endpoint to use like https://mytextanalyticsresource.cognitive.azure.com. + public CluApplication(string projectName, string deploymentName, string endpointKey, string endpoint) + : this((projectName, deploymentName, endpointKey, endpoint)) + { + } + + private CluApplication(ValueTuple props) + { + var (projectName, deploymentName, endpointKey, endpoint) = props; + + if (string.IsNullOrWhiteSpace(projectName)) + { + throw new ArgumentNullException("projectName value is Null or whitespace. Please use a valid projectName."); + } + + if (string.IsNullOrWhiteSpace(deploymentName)) + { + throw new ArgumentNullException("deploymentName value is Null or whitespace. Please use a valid deploymentName."); + } + + if (string.IsNullOrWhiteSpace(endpointKey)) + { + throw new ArgumentNullException("endpointKey value is Null or whitespace. Please use a valid endpointKey."); + } + + if (string.IsNullOrWhiteSpace(endpoint)) + { + throw new ArgumentNullException("Endpoint value is Null or whitespace. Please use a valid endpoint."); + } + + if (!Guid.TryParse(endpointKey, out var _)) + { + throw new ArgumentException($"\"{endpointKey}\" is not a valid CLU subscription key."); + } + + if (!Uri.IsWellFormedUriString(endpoint, UriKind.Absolute)) + { + throw new ArgumentException($"\"{endpoint}\" is not a valid CLU endpoint."); + } + + ProjectName = projectName; + DeploymentName = deploymentName; + EndpointKey = endpointKey; + Endpoint = endpoint; + } + + /// + /// Gets or sets the CLU project name. + /// + /// + /// CLU project name. + /// + public string ProjectName { get; set; } + + /// + /// Gets or sets CLU model deployment name. + /// + /// + /// CLU model deployment name. + /// + public string DeploymentName { get; set; } + + /// + /// Gets or sets CLU subscription or endpoint key. + /// + /// + /// CLU subscription or endpoint key. + /// + public string EndpointKey { get; set; } + + /// + /// Gets or sets CLU endpoint like https://mytextanalyticsresource.cognitive.azure.com. + /// + /// + /// CLU endpoint where application is hosted. + /// + public string Endpoint { get; set; } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluEntity.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluEntity.cs new file mode 100644 index 0000000000..9c0649f2d5 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluEntity.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; + +namespace Microsoft.BotBuilderSamples.Clu +{ + public class CluEntity + { + [JsonProperty("category")] + public string Category { get; set; } + + [JsonProperty("text")] + public string Text { get; set; } + + [JsonProperty("offset")] + public int Offset { get; set; } + + [JsonProperty("length")] + public int Length { get; set; } + + [JsonProperty("confidenceScore")] + public float ConfidenceScore { get; set; } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluOptions.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluOptions.cs new file mode 100644 index 0000000000..d773bf4d99 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluOptions.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Azure.AI.Language.Conversations; + +namespace Microsoft.BotBuilderSamples.Clu +{ + /// + /// Options for interacting with the CLU service. + /// + public class CluOptions + { + /// + /// Creates an instance of containing the CLU Application as well as optional configurations. + /// + public CluOptions(CluApplication app) + { + CluApplication = app; + } + + /// + /// An instance of the class containing connection details for your CLU application. + /// + public CluApplication CluApplication { get; } + + /// + /// If true, the query will be kept by the service for customers to further review, to improve the model quality. + /// + public bool? IsLoggingEnabled { get; set; } + + /// + /// The language to be used with this recognizer. + /// + public string Language { get; set; } + + /// + /// If set to true, the service will return a more verbose response. + /// + public bool? Verbose { get; set; } + + /// + /// The name of the target project this request is sending to directly. + /// + public string DirectTarget { get; set; } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluRecognizer.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluRecognizer.cs new file mode 100644 index 0000000000..fb082fde0a --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/CluRecognizer.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Azure; +using Azure.AI.Language.Conversations; +using Azure.Core; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.TraceExtensions; +using Newtonsoft.Json.Linq; + +namespace Microsoft.BotBuilderSamples.Clu +{ + /// + /// Class for a recognizer that utilizes the CLU service. + /// + public class CluRecognizer : IRecognizer + { + /// + /// The context label for a CLU trace activity. + /// + private const string CluTraceLabel = "CLU Trace"; + + /// + /// Key used when adding Question Answering into to intents collection. + /// + public const string QuestionAnsweringMatchIntent = "QuestionAnsweringMatch"; + + /// + /// The Conversation Analysis Client instance that handles calls to the service. + /// + private readonly ConversationAnalysisClient _conversationsClient; + + /// + /// CLU Recognizer Options + /// + private readonly CluOptions _options; + + /// + /// The CluRecognizer constructor. + /// + public CluRecognizer(CluOptions options, ConversationAnalysisClient conversationAnalysisClient = default) + { + // for mocking purposes + _conversationsClient = conversationAnalysisClient ?? new ConversationAnalysisClient( + new Uri(options.CluApplication.Endpoint), + new AzureKeyCredential(options.CluApplication.EndpointKey)); + _options = options; + } + + /// + /// The RecognizeAsync function used to recognize the intents and entities in the utterance present in the turn context. + /// The function uses the options provided in the constructor of the CluRecognizer object. + /// + public async Task RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + return await RecognizeInternalAsync(turnContext?.Activity?.AsMessageActivity()?.Text, turnContext, cancellationToken); + } + + /// + /// The RecognizeAsync overload of template type T that allows the user to define their own implementation of the IRecognizerConvert class. + /// + public async Task RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken) + where T : IRecognizerConvert, new() + { + var result = new T(); + result.Convert(await RecognizeInternalAsync(turnContext?.Activity?.AsMessageActivity()?.Text, turnContext, cancellationToken)); + return result; + } + + private async Task RecognizeInternalAsync(string utterance, ITurnContext turnContext, CancellationToken cancellationToken) + { + + var request = new + { + analysisInput = new + { + conversationItem = new + { + text = utterance, + id = "1", + participantId = "1", + } + }, + parameters = new + { + projectName = _options.CluApplication.ProjectName, + deploymentName = _options.CluApplication.DeploymentName, + + // Use Utf16CodeUnit for strings in .NET. + stringIndexType = "Utf16CodeUnit", + }, + kind = "Conversation", + }; + + + var cluResponse = await _conversationsClient.AnalyzeConversationAsync(RequestContent.Create(request)); + using JsonDocument result = JsonDocument.Parse(cluResponse.ContentStream); + var recognizerResult = RecognizerResultBuilder.BuildRecognizerResultFromCluResponse(result, utterance); + + var traceInfo = JObject.FromObject( + new + { + response = result, + recognizerResult, + }); + + await turnContext.TraceActivityAsync("CLU Recognizer", traceInfo, nameof(CluRecognizer), CluTraceLabel, cancellationToken); + + return recognizerResult; + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Clu/RecognizerResultBuilder.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/RecognizerResultBuilder.cs new file mode 100644 index 0000000000..e8abacaa3a --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Clu/RecognizerResultBuilder.cs @@ -0,0 +1,283 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using Azure.AI.Language.Conversations; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.BotBuilderSamples.Clu +{ + /// + /// A helper class that creates and populate from a instance. + /// + internal static class RecognizerResultBuilder + { + private const string MetadataKey = "$instance"; + + private static readonly HashSet DateSubtypes = new HashSet + { + "date", + "daterange", + "datetime", + "datetimerange", + "duration", + "set", + "time", + "timerange" + }; + + private static readonly HashSet GeographySubtypes = new HashSet + { + "poi", + "city", + "countryRegion", + "continent", + "state" + }; + + public static RecognizerResult BuildRecognizerResultFromCluResponse(JsonDocument cluResult, string utterance) + { + + JsonElement conversationalTaskResult = cluResult.RootElement; + JsonElement conversationPrediction = conversationalTaskResult.GetProperty("result").GetProperty("prediction"); + + + var recognizerResult = new RecognizerResult + { + Text = utterance, + AlteredText = conversationalTaskResult.GetProperty("result").GetProperty("query").GetString() + }; + + UpdateRecognizerResultFromConversations(conversationPrediction, recognizerResult); + + + AddProperties(conversationPrediction, recognizerResult); + + return recognizerResult; + } + + /// + /// Returns a RecognizerResult from a conversations project response. + /// + /// Intents: List of Intents with their confidence scores. + /// Entities: has the object: { "entities" : [{entity1}, {entity2}] } + /// Properties: Additional information returned by the service. + /// + /// + private static void UpdateRecognizerResultFromConversations(JsonElement conversationPrediction, RecognizerResult recognizerResult) + { + recognizerResult.Intents = GetIntents(conversationPrediction); + recognizerResult.Entities = ExtractEntitiesAndMetadata(conversationPrediction); + } + + private static IDictionary GetIntents(JsonElement prediction) + { + var result = new Dictionary(); + foreach (var intent in prediction.GetProperty("intents").EnumerateArray()) + { + result.Add(intent.GetProperty("category").GetString(), new IntentScore { Score = intent.GetProperty("confidenceScore").GetSingle() }); + } + + return result; + } + + private static JObject ExtractEntitiesAndMetadata(JsonElement prediction) + { + var entities = prediction.GetProperty("entities").GetRawText(); // Requires refactoring + //var entityObject = JsonConvert.SerializeObject(entities); + var jsonArray = JArray.Parse(entities); + var returnedObject = new JObject { { "entities", jsonArray } }; + + return returnedObject; + } + + private static void AddProperties(JsonElement conversationPrediction, RecognizerResult result) + { + var topIntent = conversationPrediction.GetProperty("topIntent").GetString(); + var projectKind = conversationPrediction.GetProperty("projectKind").GetString(); + + result.Properties.Add("projectKind", projectKind.ToString()); + + if (topIntent != null) + { + result.Properties.Add("topIntent", topIntent); + } + } + + private static IDictionary GetIntents(JObject luisResult) + { + var result = new Dictionary(); + var intents = (JObject)luisResult["intents"]; + if (intents != null) + { + foreach (var intent in intents) + { + result.Add(NormalizeIntent(intent.Key), new IntentScore { Score = intent.Value["score"]?.Value() ?? 0.0 }); + } + } + + return result; + } + + private static JObject ExtractEntitiesAndMetadata(JObject prediction) + { + var entities = JObject.FromObject(prediction["entities"]); + return (JObject)MapProperties(entities, false); + } + + private static void AddProperties(JObject luis, RecognizerResult result) + { + var sentiment = luis["sentiment"]; + if (luis["sentiment"] != null) + { + result.Properties.Add("sentiment", new JObject( + new JProperty("label", sentiment["label"]), + new JProperty("score", sentiment["score"]))); + } + } + + private static string NormalizeIntent(string intent) + { + return intent.Replace('.', '_').Replace(' ', '_'); + } + + private static string NormalizeEntity(string entity) + { + // Type::Role -> Role + var type = entity.Split(':').Last(); + return type.Replace('.', '_').Replace(' ', '_'); + } + + private static JToken MapProperties(JToken source, bool inInstance) + { + var result = source; + if (source is JObject obj) + { + var nobj = new JObject(); + + // Fix datetime by reverting to simple timex + if (!inInstance && obj.TryGetValue("type", out var type) && type.Type == JTokenType.String && DateSubtypes.Contains(type.Value())) + { + var timexs = obj["values"]; + var arr = new JArray(); + if (timexs != null) + { + var unique = new HashSet(); + foreach (var elt in timexs) + { + unique.Add(elt["timex"]?.Value()); + } + + foreach (var timex in unique) + { + arr.Add(timex); + } + + nobj["timex"] = arr; + } + + nobj["type"] = type; + } + else + { + // Map or remove properties + foreach (var property in obj.Properties()) + { + var name = NormalizeEntity(property.Name); + var isArray = property.Value.Type == JTokenType.Array; + var isString = property.Value.Type == JTokenType.String; + var isInt = property.Value.Type == JTokenType.Integer; + var val = MapProperties(property.Value, inInstance || property.Name == MetadataKey); + if (name == "datetime" && isArray) + { + nobj.Add("datetimeV1", val); + } + else if (name == "datetimeV2" && isArray) + { + nobj.Add("datetime", val); + } + else if (inInstance) + { + // Correct $instance issues + if (name == "length" && isInt) + { + nobj.Add("endIndex", property.Value.Value() + property.Parent["startIndex"].Value()); + } + else if (!((isInt && name == "modelTypeId") || + (isString && name == "role"))) + { + nobj.Add(name, val); + } + } + else + { + // Correct non-$instance values + if (name == "unit" && isString) + { + nobj.Add("units", val); + } + else + { + nobj.Add(name, val); + } + } + } + } + + result = nobj; + } + else if (source is JArray arr) + { + var narr = new JArray(); + foreach (var elt in arr) + { + // Check if element is geographyV2 + var isGeographyV2 = string.Empty; + foreach (var props in elt.Children()) + { + var tokenProp = props as JProperty; + if (tokenProp == null) + { + break; + } + + if (tokenProp.Name.Contains("type") && GeographySubtypes.Contains(tokenProp.Value.ToString())) + { + isGeographyV2 = tokenProp.Value.ToString(); + break; + } + } + + if (!inInstance && !string.IsNullOrEmpty(isGeographyV2)) + { + var geoEntity = new JObject(); + foreach (var props in elt.Children()) + { + var tokenProp = (JProperty)props; + if (tokenProp.Name.Contains("value")) + { + geoEntity.Add("location", tokenProp.Value); + } + } + + geoEntity.Add("type", isGeographyV2); + narr.Add(geoEntity); + } + else + { + narr.Add(MapProperties(elt, inInstance)); + } + } + + result = narr; + } + + return result; + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBooking.cs b/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBooking.cs new file mode 100644 index 0000000000..97ec4b6bfa --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBooking.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Bot.Builder; +using Microsoft.BotBuilderSamples.Clu; +using Newtonsoft.Json; + +namespace Microsoft.BotBuilderSamples +{ + /// + /// An implementation that provides helper methods and properties to interact with + /// the CLU recognizer results. + /// + public class FlightBooking : IRecognizerConvert + { + public enum Intent + { + BookFlight, + Cancel, + GetWeather, + None + } + + public string Text { get; set; } + + public string AlteredText { get; set; } + + public Dictionary Intents { get; set; } + + public CluEntities Entities { get; set; } + + public IDictionary Properties { get; set; } + + public void Convert(dynamic result) + { + var jsonResult = JsonConvert.SerializeObject(result, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + var app = JsonConvert.DeserializeObject(jsonResult); + + Text = app.Text; + AlteredText = app.AlteredText; + Intents = app.Intents; + Entities = app.Entities; + Properties = app.Properties; + } + + public (Intent intent, double score) GetTopIntent() + { + var maxIntent = Intent.None; + var max = 0.0; + foreach (var entry in Intents) + { + if (entry.Value.Score > max) + { + maxIntent = entry.Key; + max = entry.Value.Score.Value; + } + } + + return (maxIntent, max); + } + + public class CluEntities + { + public CluEntity[] Entities; + + public CluEntity[] GetFromCityList() => Entities.Where(e => e.Category == "fromCity").ToArray(); + + public CluEntity[] GetToCityList() => Entities.Where(e => e.Category == "toCity").ToArray(); + + public CluEntity[] GetFlightDateList() => Entities.Where(e => e.Category == "flightDate").ToArray(); + + public string GetFromCity() => GetFromCityList().FirstOrDefault()?.Text; + + public string GetToCity() => GetToCityList().FirstOrDefault()?.Text; + + public string GetFlightDate() => GetFlightDateList().FirstOrDefault()?.Text; + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBooking.json b/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBooking.json new file mode 100644 index 0000000000..61f4a2d86c --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBooking.json @@ -0,0 +1,626 @@ +{ + "api-version": "2022-03-01-preview", + "stringIndexType": "Utf16CodeUnit", + "metadata": { + "projectKind": "conversation", + "settings": { + "confidenceThreshold": 0 + }, + "projectName": "FlightBooking", + "multilingual": false, + "description": "CLU Model for CoreBot", + "language": "en-us" + }, + "assets": { + "intents": [ + { + "category": "BookFlight" + }, + { + "category": "Cancel" + }, + { + "category": "GetWeather" + }, + { + "category": "None" + } + ], + "entities": [ + { + "category": "toCity", + "compositionSetting": "returnUnion" + }, + { + "category": "fromCity", + "compositionSetting": "returnUnion" + }, + { + "category": "flightDate", + "compositionSetting": "returnUnion", + "prebuilts": [ + { + "category": "DateTime" + } + ] + } + ], + "utterances": [ + { + "text": "a flight to Washington from Cairo", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 12, + "length": 10 + }, + { + "category": "fromCity", + "offset": 28, + "length": 5 + } + ] + }, + { + "text": "a ticket to Shanghai from Tokyo", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 12, + "length": 8 + }, + { + "category": "fromCity", + "offset": 26, + "length": 5 + } + ] + }, + { + "text": "To Tokyo from Cairo", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 3, + "length": 5 + }, + { + "category": "fromCity", + "offset": 14, + "length": 5 + } + ] + }, + { + "text": "To London from Paris", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 3, + "length": 6 + }, + { + "category": "fromCity", + "offset": 15, + "length": 5 + } + ] + }, + { + "text": "from Cairo to Barcelona", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 5, + "length": 5 + }, + { + "category": "toCity", + "offset": 14, + "length": 9 + } + ] + }, + { + "text": "On the third week of February", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "flightDate", + "offset": 7, + "length": 22 + } + ] + }, + { + "text": "to New Delhi", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 3, + "length": 9 + } + ] + }, + { + "text": "the source city is New Delhi", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 19, + "length": 9 + } + ] + }, + { + "text": "the destination city is Cairo", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 24, + "length": 5 + } + ] + }, + { + "text": "to London", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 3, + "length": 6 + } + ] + }, + { + "text": "Book a flight on the first of October", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "flightDate", + "offset": 21, + "length": 16 + } + ] + }, + { + "text": "Book a flight from Barcelona to London", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 19, + "length": 9 + }, + { + "category": "toCity", + "offset": 32, + "length": 6 + } + ] + }, + { + "text": "I want a flight on the second of December 2022 ", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "flightDate", + "offset": 23, + "length": 23 + } + ] + }, + { + "text": "flight from London", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 12, + "length": 6 + } + ] + }, + { + "text": "ticket to New York", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 10, + "length": 8 + } + ] + }, + { + "text": "I want to book a flight from Cairo to Shanghai on 22/12/2021", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 29, + "length": 5 + }, + { + "category": "toCity", + "offset": 38, + "length": 8 + }, + { + "category": "flightDate", + "offset": 50, + "length": 10 + } + ] + }, + { + "text": "buy a ticket from new york to london", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 18, + "length": 8 + }, + { + "category": "toCity", + "offset": 30, + "length": 6 + } + ] + }, + { + "text": "buy a ticket from paris to cairo", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 18, + "length": 5 + }, + { + "category": "toCity", + "offset": 27, + "length": 5 + } + ] + }, + { + "text": "buy a ticket from cairo to new york", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 18, + "length": 5 + }, + { + "category": "toCity", + "offset": 27, + "length": 8 + } + ] + }, + { + "text": "book a flight from paris to london", + "language": "en-us", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 19, + "length": 5 + }, + { + "category": "toCity", + "offset": 28, + "length": 6 + } + ] + }, + { + "text": "book a flight", + "intent": "BookFlight", + "entities": [] + }, + { + "text": "book a flight from new york", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 19, + "length": 8 + } + ] + }, + { + "text": "book a flight from seattle", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 19, + "length": 7 + } + ] + }, + { + "text": "book a hotel in new york", + "intent": "None", + "entities": [ + { + "category": "fromCity", + "offset": 16, + "length": 8 + } + ] + }, + { + "text": "book a restaurant", + "intent": "None", + "entities": [] + }, + { + "text": "book flight from london to paris on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 17, + "length": 6 + }, + { + "category": "toCity", + "offset": 27, + "length": 5 + }, + { + "category": "flightDate", + "offset": 36, + "length": 8 + } + ] + }, + { + "text": "book flight to berlin on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 15, + "length": 6 + }, + { + "category": "flightDate", + "offset": 25, + "length": 8 + } + ] + }, + { + "text": "book me a flight from london to paris", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 22, + "length": 6 + }, + { + "category": "toCity", + "offset": 32, + "length": 5 + } + ] + }, + { + "text": "bye", + "intent": "Cancel", + "entities": [] + }, + { + "text": "cancel booking", + "intent": "Cancel", + "entities": [] + }, + { + "text": "exit", + "intent": "Cancel", + "entities": [] + }, + { + "text": "find an airport near me", + "intent": "None", + "entities": [] + }, + { + "text": "flight to paris", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 10, + "length": 5 + } + ] + }, + { + "text": "flight to paris from london on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 10, + "length": 5 + }, + { + "category": "fromCity", + "offset": 21, + "length": 6 + }, + { + "category": "flightDate", + "offset": 31, + "length": 8 + } + ] + }, + { + "text": "fly from berlin to paris on may 5th", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 9, + "length": 6 + }, + { + "category": "toCity", + "offset": 19, + "length": 5 + }, + { + "category": "flightDate", + "offset": 28, + "length": 7 + } + ] + }, + { + "text": "go to paris", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 6, + "length": 5 + } + ] + }, + { + "text": "going from paris to berlin", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 11, + "length": 5 + }, + { + "category": "toCity", + "offset": 20, + "length": 6 + } + ] + }, + { + "text": "i'd like to rent a car", + "intent": "None", + "entities": [] + }, + { + "text": "ignore", + "intent": "Cancel", + "entities": [] + }, + { + "text": "travel from new york to paris", + "intent": "BookFlight", + "entities": [ + { + "category": "fromCity", + "offset": 12, + "length": 8 + }, + { + "category": "toCity", + "offset": 24, + "length": 5 + } + ] + }, + { + "text": "travel to new york", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 10, + "length": 8 + } + ] + }, + { + "text": "travel to paris", + "intent": "BookFlight", + "entities": [ + { + "category": "toCity", + "offset": 10, + "length": 5 + } + ] + }, + { + "text": "what's the forecast for this friday?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like for tomorrow", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like in new york", + "intent": "GetWeather", + "entities": [ + { + "category": "fromCity", + "offset": 27, + "length": 8 + } + ] + }, + { + "text": "what's the weather like?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "winter is coming", + "intent": "None", + "entities": [] + } + ] + } +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBookingEx.cs b/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBookingEx.cs new file mode 100644 index 0000000000..327289dc0e --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/CognitiveModels/FlightBookingEx.cs @@ -0,0 +1,6 @@ +namespace CoreBotCLU.CognitiveModels +{ + public class FlightBookingEx + { + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Controllers/BotController.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Controllers/BotController.cs new file mode 100644 index 0000000000..c3262a97f0 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Controllers/BotController.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Microsoft.BotBuilderSamples.Controllers +{ + // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + + public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) + { + _adapter = adapter; + _bot = bot; + } + + [HttpPost, HttpGet] + public async Task PostAsync() + { + // Delegate the processing of the HTTP POST to the adapter. + // The adapter will invoke the bot. + await _adapter.ProcessAsync(Request, Response, _bot); + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/CoreBotCLU.csproj b/samples/csharp_dotnetcore/84.core-bot-clu/CoreBotCLU.csproj new file mode 100644 index 0000000000..1824951ea8 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/CoreBotCLU.csproj @@ -0,0 +1,30 @@ + + + + net6.0 + latest + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentScripts/Linux/.deployment b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentScripts/Linux/.deployment new file mode 100644 index 0000000000..1047846624 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentScripts/Linux/.deployment @@ -0,0 +1,2 @@ +[config] +command = ./deploy.sh \ No newline at end of file diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentScripts/Linux/deploy.sh b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentScripts/Linux/deploy.sh new file mode 100644 index 0000000000..f607879d71 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentScripts/Linux/deploy.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +# ---------------------- +# KUDU Deployment Script +# Version: 1.0.17 +# ---------------------- + +# Helpers +# ------- + +exitWithMessageOnError () { + if [ ! $? -eq 0 ]; then + echo "An error has occurred during web site deployment." + echo $1 + exit 1 + fi +} + +# Prerequisites +# ------------- + +# Verify node.js installed +hash node 2>/dev/null +exitWithMessageOnError "Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment." + +# Setup +# ----- + +SCRIPT_DIR="${BASH_SOURCE[0]%\\*}" +SCRIPT_DIR="${SCRIPT_DIR%/*}" +ARTIFACTS=$SCRIPT_DIR/../artifacts +KUDU_SYNC_CMD=${KUDU_SYNC_CMD//\"} + +if [[ ! -n "$DEPLOYMENT_SOURCE" ]]; then + DEPLOYMENT_SOURCE=$SCRIPT_DIR +fi + +if [[ ! -n "$NEXT_MANIFEST_PATH" ]]; then + NEXT_MANIFEST_PATH=$ARTIFACTS/manifest + + if [[ ! -n "$PREVIOUS_MANIFEST_PATH" ]]; then + PREVIOUS_MANIFEST_PATH=$NEXT_MANIFEST_PATH + fi +fi + +if [[ ! -n "$DEPLOYMENT_TARGET" ]]; then + DEPLOYMENT_TARGET=$ARTIFACTS/wwwroot +else + KUDU_SERVICE=true +fi + +if [[ ! -n "$KUDU_SYNC_CMD" ]]; then + # Install kudu sync + echo Installing Kudu Sync + npm install kudusync -g --silent + exitWithMessageOnError "npm failed" + + if [[ ! -n "$KUDU_SERVICE" ]]; then + # In case we are running locally this is the correct location of kuduSync + KUDU_SYNC_CMD=kuduSync + else + # In case we are running on kudu service this is the correct location of kuduSync + KUDU_SYNC_CMD=$APPDATA/npm/node_modules/kuduSync/bin/kuduSync + fi +fi + +if [ "x$DEPLOYMENT_TEMP" = x ]; then + DEPLOYMENT_TEMP=/tmp/`date +%s` + CLEAN_LOCAL_DEPLOYMENT_TEMP=true +fi + +if [ "x$CLEAN_LOCAL_DEPLOYMENT_TEMP" = xtrue ]; then + rm -rf "$DEPLOYMENT_TEMP" + mkdir "$DEPLOYMENT_TEMP" +fi + +################################################################################################################################## +# Deployment +# ---------- + +echo Handling ASP.NET Core Web Application deployment. + +# 1. KuduSync +"$KUDU_SYNC_CMD" -v 50 -f "./publishedbot" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh" +exitWithMessageOnError "Kudu Sync failed" + + +################################################################################################################################## +echo "Finished successfully." diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/LinuxDotNet/template.json b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/LinuxDotNet/template.json new file mode 100644 index 0000000000..c6997be51a --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/LinuxDotNet/template.json @@ -0,0 +1,307 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "botName": { + "defaultValue": "nightly-build-linux", + "type": "String" + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." + } + }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + }, + "existingUserAssignedMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "existingUserAssignedMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." + } + } + }, + "variables": { + "siteHost": "[concat(parameters('botName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "msiResourceId": "[concat(subscription().id, '/resourceGroups/', parameters('existingUserAssignedMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('existingUserAssignedMSIName'))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[parameters('tenantId')]", + "msiResourceId": "", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[parameters('tenantId')]", + "msiResourceId": "[variables('msiResourceId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } + }, + "resources": [ + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2016-09-01", + "name": "[parameters('botName')]", + "location": "West US", + "sku": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "kind": "linux", + "properties": { + "name": "[parameters('botName')]", + "perSiteScaling": false, + "reserved": true, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2016-08-01", + "name": "[parameters('botName')]", + "identity": "[variables('appType').identity]", + "location": "West US", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('botName'))]" + ], + "kind": "app,linux", + "properties": { + "enabled": true, + "hostNameSslStates": [ + { + "name": "[concat(parameters('botName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('botName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('botName'))]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" + } + ] + }, + "reserved": true, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2016-08-01", + "name": "[concat(parameters('botName'), '/web')]", + "location": "West US", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "phpVersion": "", + "pythonVersion": "", + "nodeVersion": "", + "linuxFxVersion": "DOTNETCORE|2.2", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "parameters('botName')", + "scmType": "LocalGit", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "appCommandLine": "", + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true, + "virtualDirectories": null + } + ], + "winAuthAdminState": 0, + "winAuthTenantState": 0, + "customAppPoolIdentityAdminState": false, + "customAppPoolIdentityTenantState": false, + "loadBalancing": "LeastRequests", + "routingRules": [], + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "vnetName": "", + "siteAuthEnabled": false, + "siteAuthSettings": { + "enabled": null, + "unauthenticatedClientAction": null, + "tokenStoreEnabled": null, + "allowedExternalRedirectUrls": null, + "defaultProvider": null, + "clientId": null, + "clientSecret": null, + "clientSecretCertificateThumbprint": null, + "issuer": null, + "allowedAudiences": null, + "additionalLoginParams": null, + "isAadAutoProvisioned": false, + "googleClientId": null, + "googleClientSecret": null, + "googleOAuthScopes": null, + "facebookAppId": null, + "facebookAppSecret": null, + "facebookOAuthScopes": null, + "twitterConsumerKey": null, + "twitterConsumerSecret": null, + "microsoftAccountClientId": null, + "microsoftAccountClientSecret": null, + "microsoftAccountOAuthScopes": null + }, + "localMySqlEnabled": false, + "http20Enabled": true, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2021-03-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botName')]", + "location": "global", + "kind": "azurebot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botName')]", + "displayName": "[parameters('botName')]", + "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppMSIResourceId": "[variables('appType').msiResourceId]", + "msaAppType": "[parameters('appType')]", + "luisAppIds": [], + "schemaTransformationVersion": "1.3", + "isCmekEnabled": false, + "isIsolated": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', parameters('botName'))]" + ] + }, + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2016-08-01", + "name": "[concat(parameters('botName'), '/', parameters('botName'), '.azurewebsites.net')]", + "location": "West US", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "siteName": "parameters('botName')", + "hostNameType": "Verified" + } + } + ] +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/new-rg-parameters.json b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/new-rg-parameters.json new file mode 100644 index 0000000000..1fee029687 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/new-rg-parameters.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "appType": { + "value": "MultiTenant" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + }, + "tenantId": { + "value": "" + }, + "existingUserAssignedMSIName": { + "value": "" + }, + "existingUserAssignedMSIResourceGroupName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/preexisting-rg-parameters.json b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 0000000000..bf1a5d4bc5 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "appType": { + "value": "MultiTenant" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + }, + "tenantId": { + "value": "" + }, + "existingUserAssignedMSIName": { + "value": "" + }, + "existingUserAssignedMSIResourceGroupName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/template-with-new-rg.json b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/template-with-new-rg.json new file mode 100644 index 0000000000..89a600e9b9 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/template-with-new-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." + } + }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + }, + "existingUserAssignedMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "existingUserAssignedMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]", + "msiResourceId": "[concat(subscription().id, '/resourceGroups/', parameters('existingUserAssignedMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('existingUserAssignedMSIName'))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[parameters('tenantId')]", + "msiResourceId": "", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[parameters('tenantId')]", + "msiResourceId": "[variables('msiResourceId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('appServicePlanName')]" + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "identity": "[variables('appType').identity]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + }, + "webSocketsEnabled": true + } + } + }, + { + "apiVersion": "2021-03-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "azurebot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppMSIResourceId": "[variables('appType').msiResourceId]", + "msaAppType": "[parameters('appType')]", + "luisAppIds": [], + "schemaTransformationVersion": "1.3", + "isCmekEnabled": false, + "isIsolated": false + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/template-with-preexisting-rg.json b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 0000000000..7c398743e3 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/DeploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,230 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." + } + }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + }, + "existingUserAssignedMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "existingUserAssignedMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "msiResourceId": "[concat(subscription().id, '/resourceGroups/', parameters('existingUserAssignedMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('existingUserAssignedMSIName'))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[parameters('tenantId')]", + "msiResourceId": "", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[parameters('tenantId')]", + "msiResourceId": "[variables('msiResourceId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('servicePlanName')]" + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "identity": "[variables('appType').identity]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + }, + "webSocketsEnabled": true + } + } + }, + { + "apiVersion": "2021-03-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "azurebot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppMSIResourceId": "[variables('appType').msiResourceId]", + "msaAppType": "[parameters('appType')]", + "luisAppIds": [], + "schemaTransformationVersion": "1.3", + "isCmekEnabled": false, + "isIsolated": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/BookingDialog.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/BookingDialog.cs new file mode 100644 index 0000000000..ae582af6ec --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/BookingDialog.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Recognizers.Text.DataTypes.TimexExpression; + +namespace Microsoft.BotBuilderSamples.Dialogs +{ + public class BookingDialog : CancelAndHelpDialog + { + private const string DestinationStepMsgText = "Where would you like to travel to?"; + private const string OriginStepMsgText = "Where are you traveling from?"; + + public BookingDialog() + : base(nameof(BookingDialog)) + { + AddDialog(new TextPrompt(nameof(TextPrompt))); + AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); + AddDialog(new DateResolverDialog()); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] + { + DestinationStepAsync, + OriginStepAsync, + TravelDateStepAsync, + ConfirmStepAsync, + FinalStepAsync, + })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task DestinationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + if (bookingDetails.Destination == null) + { + var promptMessage = MessageFactory.Text(DestinationStepMsgText, DestinationStepMsgText, InputHints.ExpectingInput); + return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); + } + + return await stepContext.NextAsync(bookingDetails.Destination, cancellationToken); + } + + private async Task OriginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + bookingDetails.Destination = (string)stepContext.Result; + + if (bookingDetails.Origin == null) + { + var promptMessage = MessageFactory.Text(OriginStepMsgText, OriginStepMsgText, InputHints.ExpectingInput); + return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); + } + + return await stepContext.NextAsync(bookingDetails.Origin, cancellationToken); + } + + private async Task TravelDateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + bookingDetails.Origin = (string)stepContext.Result; + + if (bookingDetails.TravelDate == null || IsAmbiguous(bookingDetails.TravelDate)) + { + return await stepContext.BeginDialogAsync(nameof(DateResolverDialog), bookingDetails.TravelDate, cancellationToken); + } + + return await stepContext.NextAsync(bookingDetails.TravelDate, cancellationToken); + } + + private async Task ConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + bookingDetails.TravelDate = (string)stepContext.Result; + + var messageText = $"Please confirm, I have you traveling to: {bookingDetails.Destination} from: {bookingDetails.Origin} on: {bookingDetails.TravelDate}. Is this correct?"; + var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput); + + return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + if ((bool)stepContext.Result) + { + var bookingDetails = (BookingDetails)stepContext.Options; + + return await stepContext.EndDialogAsync(bookingDetails, cancellationToken); + } + + return await stepContext.EndDialogAsync(null, cancellationToken); + } + + private static bool IsAmbiguous(string timex) + { + var timexProperty = new TimexProperty(timex); + return !timexProperty.Types.Contains(Constants.TimexTypes.Definite); + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/CancelAndHelpDialog.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/CancelAndHelpDialog.cs new file mode 100644 index 0000000000..d97388d5db --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/CancelAndHelpDialog.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; + +namespace Microsoft.BotBuilderSamples.Dialogs +{ + public class CancelAndHelpDialog : ComponentDialog + { + private const string HelpMsgText = "Show help here"; + private const string CancelMsgText = "Cancelling..."; + + public CancelAndHelpDialog(string id) + : base(id) + { + } + + protected override async Task OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default) + { + var result = await InterruptAsync(innerDc, cancellationToken); + if (result != null) + { + return result; + } + + return await base.OnContinueDialogAsync(innerDc, cancellationToken); + } + + private async Task InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken) + { + if (innerDc.Context.Activity.Type == ActivityTypes.Message) + { + var text = innerDc.Context.Activity.Text.ToLowerInvariant(); + + switch (text) + { + case "help": + case "?": + var helpMessage = MessageFactory.Text(HelpMsgText, HelpMsgText, InputHints.ExpectingInput); + await innerDc.Context.SendActivityAsync(helpMessage, cancellationToken); + return new DialogTurnResult(DialogTurnStatus.Waiting); + + case "cancel": + case "quit": + var cancelMessage = MessageFactory.Text(CancelMsgText, CancelMsgText, InputHints.IgnoringInput); + await innerDc.Context.SendActivityAsync(cancelMessage, cancellationToken); + return await innerDc.CancelAllDialogsAsync(cancellationToken); + } + } + + return null; + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/DateResolverDialog.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/DateResolverDialog.cs new file mode 100644 index 0000000000..acb3409e29 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/DateResolverDialog.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Recognizers.Text.DataTypes.TimexExpression; + +namespace Microsoft.BotBuilderSamples.Dialogs +{ + public class DateResolverDialog : CancelAndHelpDialog + { + private const string PromptMsgText = "When would you like to travel?"; + private const string RepromptMsgText = "I'm sorry, to make your booking please enter a full travel date including Day Month and Year."; + + public DateResolverDialog(string id = null) + : base(id ?? nameof(DateResolverDialog)) + { + AddDialog(new DateTimePrompt(nameof(DateTimePrompt), DateTimePromptValidator)); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] + { + InitialStepAsync, + FinalStepAsync, + })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var timex = (string)stepContext.Options; + + var promptMessage = MessageFactory.Text(PromptMsgText, PromptMsgText, InputHints.ExpectingInput); + var repromptMessage = MessageFactory.Text(RepromptMsgText, RepromptMsgText, InputHints.ExpectingInput); + + if (timex == null) + { + // We were not given any date at all so prompt the user. + return await stepContext.PromptAsync(nameof(DateTimePrompt), + new PromptOptions + { + Prompt = promptMessage, + RetryPrompt = repromptMessage, + }, cancellationToken); + } + + // We have a Date we just need to check it is unambiguous. + // This is disabled for now since Resolution is not yet included in CLU response. + + //var timexProperty = new TimexProperty(timex); + //if (!timexProperty.Types.Contains(Constants.TimexTypes.Definite)) + //{ + // // This is essentially a "reprompt" of the data we were given up front. + // return await stepContext.PromptAsync(nameof(DateTimePrompt), + // new PromptOptions + // { + // Prompt = repromptMessage, + // }, cancellationToken); + //} + + return await stepContext.NextAsync(new List { new DateTimeResolution { Timex = timex } }, cancellationToken); + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var timex = ((List)stepContext.Result)[0].Timex; + return await stepContext.EndDialogAsync(timex, cancellationToken); + } + + private static Task DateTimePromptValidator(PromptValidatorContext> promptContext, CancellationToken cancellationToken) + { + if (promptContext.Recognized.Succeeded) + { + // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part. + // TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year. + var timex = promptContext.Recognized.Value[0].Timex.Split('T')[0]; + + // If this is a definite Date including year, month and day we are good otherwise reprompt. + // A better solution might be to let the user know what part is actually missing. + var isDefinite = new TimexProperty(timex).Types.Contains(Constants.TimexTypes.Definite); + + return Task.FromResult(isDefinite); + } + + return Task.FromResult(false); + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/MainDialog.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/MainDialog.cs new file mode 100644 index 0000000000..2940c549a4 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Dialogs/MainDialog.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; +using Microsoft.Recognizers.Text.DataTypes.TimexExpression; + +namespace Microsoft.BotBuilderSamples.Dialogs +{ + public class MainDialog : ComponentDialog + { + private readonly FlightBookingRecognizer _cluRecognizer; + protected readonly ILogger Logger; + + // Dependency injection uses this constructor to instantiate MainDialog + public MainDialog(FlightBookingRecognizer cluRecognizer, BookingDialog bookingDialog, ILogger logger) + : base(nameof(MainDialog)) + { + _cluRecognizer = cluRecognizer; + Logger = logger; + + AddDialog(new TextPrompt(nameof(TextPrompt))); + AddDialog(bookingDialog); + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] + { + IntroStepAsync, + ActStepAsync, + FinalStepAsync, + })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + if (!_cluRecognizer.IsConfigured) + { + await stepContext.Context.SendActivityAsync( + MessageFactory.Text("NOTE: CLU is not configured. To enable all capabilities, add 'CluProjectName', 'CluDeploymentName', 'CluAPIKey' and 'CluAPIHostName' to the appsettings.json file.", inputHint: InputHints.IgnoringInput), cancellationToken); + + return await stepContext.NextAsync(null, cancellationToken); + } + + // Use the text provided in FinalStepAsync or the default if it is the first time. + var weekLaterDate = DateTime.Now.AddDays(7).ToString("MMMM d, yyyy"); + var messageText = stepContext.Options?.ToString() ?? $"What can I help you with today?\nSay something like \"Book a flight from Paris to Berlin on {weekLaterDate}\""; + var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput); + return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); + } + + private async Task ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + if (!_cluRecognizer.IsConfigured) + { + // CLU is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance. + return await stepContext.BeginDialogAsync(nameof(BookingDialog), new BookingDetails(), cancellationToken); + } + + // Call CLU and gather any potential booking details. (Note the TurnContext has the response to the prompt.) + var cluResult = await _cluRecognizer.RecognizeAsync(stepContext.Context, cancellationToken); + switch (cluResult.GetTopIntent().intent) + { + case FlightBooking.Intent.BookFlight: + // Initialize BookingDetails with any entities we may have found in the response. + var bookingDetails = new BookingDetails() + { + Destination = cluResult.Entities.GetToCity(), + Origin = cluResult.Entities.GetFromCity(), + TravelDate = cluResult.Entities.GetFlightDate(), + }; + + // Run the BookingDialog giving it whatever details we have from the CLU call, it will fill out the remainder. + return await stepContext.BeginDialogAsync(nameof(BookingDialog), bookingDetails, cancellationToken); + + case FlightBooking.Intent.GetWeather: + // We haven't implemented the GetWeatherDialog so we just display a TODO message. + var getWeatherMessageText = "TODO: get weather flow here"; + var getWeatherMessage = MessageFactory.Text(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput); + await stepContext.Context.SendActivityAsync(getWeatherMessage, cancellationToken); + break; + + default: + // Catch all for unhandled intents + var didntUnderstandMessageText = $"Sorry, I didn't get that. Please try asking in a different way (intent was {cluResult.GetTopIntent().intent})"; + var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput); + await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken); + break; + } + + return await stepContext.NextAsync(null, cancellationToken); + } + + private async Task FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // If the child dialog ("BookingDialog") was cancelled, the user failed to confirm or if the intent wasn't BookFlight + // the Result here will be null. + if (stepContext.Result is BookingDetails result) + { + // Now we have all the booking details call the booking service. + + // If the call to the booking service was successful tell the user. + + var timeProperty = new TimexProperty(result.TravelDate); + var travelDateMsg = timeProperty.ToNaturalLanguage(DateTime.Now); + var messageText = $"I have you booked to {result.Destination} from {result.Origin} on {travelDateMsg}"; + var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput); + await stepContext.Context.SendActivityAsync(message, cancellationToken); + } + + // Restart the main dialog with a different message the second time around + var promptMessage = "What else can I do for you?"; + return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken); + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/FlightBookingRecognizer.cs b/samples/csharp_dotnetcore/84.core-bot-clu/FlightBookingRecognizer.cs new file mode 100644 index 0000000000..9b5f05edad --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/FlightBookingRecognizer.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.BotBuilderSamples.Clu; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotBuilderSamples +{ + public class FlightBookingRecognizer : IRecognizer + { + private readonly CluRecognizer _recognizer; + + public FlightBookingRecognizer(IConfiguration configuration) + { + var cluIsConfigured = !string.IsNullOrEmpty(configuration["CluProjectName"]) && !string.IsNullOrEmpty(configuration["CluDeploymentName"]) && !string.IsNullOrEmpty(configuration["CluAPIKey"]) && !string.IsNullOrEmpty(configuration["CluAPIHostName"]); + if (cluIsConfigured) + { + var cluApplication = new CluApplication( + configuration["CluProjectName"], + configuration["CluDeploymentName"], + configuration["CluAPIKey"], + "https://" + configuration["CluAPIHostName"]); + var recognizerOptions = new CluOptions(cluApplication) { Language = "en" }; + + _recognizer = new CluRecognizer(recognizerOptions); + } + } + + // Returns true if clu is configured in the appsettings.json and initialized. + public virtual bool IsConfigured => _recognizer != null; + + public virtual async Task RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken) + => await _recognizer.RecognizeAsync(turnContext, cancellationToken); + + public virtual async Task RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken) + where T : IRecognizerConvert, new() + => await _recognizer.RecognizeAsync(turnContext, cancellationToken); + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Program.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Program.cs new file mode 100644 index 0000000000..15de094baf --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Program.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.ConfigureLogging((logging) => + { + logging.AddDebug(); + logging.AddConsole(); + }); + webBuilder.UseStartup(); + }); + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Properties/launchSettings.json b/samples/csharp_dotnetcore/84.core-bot-clu/Properties/launchSettings.json new file mode 100644 index 0000000000..30d8357e53 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:3978/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + ".NET Core": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:3978", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/README.md b/samples/csharp_dotnetcore/84.core-bot-clu/README.md new file mode 100644 index 0000000000..3fe15b64a3 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/README.md @@ -0,0 +1,97 @@ +# CoreBotCLU + +Bot Framework v4 core bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to: + +- Use [CLU](https://language.cognitive.azure.com) to implement core AI capabilities (LUIS is no longer supported by Microsoft) +- Implement a multi-turn conversation using Dialogs +- Handle user interruptions for such things as `Help` or `Cancel` +- Prompt for and validate requests for information from the user + +## Prerequisites + +This sample **requires** prerequisites in order to run. + +### Overview + +This bot uses [CLU](https://language.cognitive.azure.com), a cloud-based API service that applies machine-learning intelligence to enable you to build natural language understanding component to be used in an end-to-end conversational application. + +### Install .NET Core CLI + +- [.NET SDK](https://dotnet.microsoft.com/download) version 6.0 + + ```bash + # determine dotnet version + dotnet --version + ``` + +### Create a CLU Project to enable language understanding + +The CLU model for this example can be found under `CognitiveModels/FlightBooking.json` and the CLU language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/azure/cognitive-services/language-service/conversational-language-understanding/tutorials/bot-framework). + +Once you created the CLU project, update `appsettings.json` with your `CluProjectName `, `CluDeploymentName `, `CluAPIKey` and `CluAPIHostName`. + +```json + "CluProjectName": "Your CLU Project Name", + "CluDeploymentName": "Your CLU Deployment Name", + "CluAPIKey ": "The resource CluAPIKey. It can be either of the keys in the Keys and Endpoint section for your Language resource in the Azure portal (https://portal.azure.com/)" + "CluAPIHostName": "The endpoint found in the Keys and Endpoint section for your Language resource in the Azure portal" +``` + +## To try this sample + +- Clone the repository + + ```bash + git clone https://github.com/microsoft/botbuilder-samples.git + ``` + +- Run the bot from a terminal or from Visual Studio: + + A) From a terminal, navigate to `samples/csharp_dotnetcore/13.core-bot-clu` + + ```bash + # run the bot + dotnet run + ``` + + B) Or from Visual Studio + + - Launch Visual Studio + - File -> Open -> Project/Solution + - Navigate to `samples/csharp_dotnetcore/13.core-bot-clu` folder + - Select `CoreBotCLU.csproj` file + - In Solution Explorer, right-click CoreBot and pick Set as Startup Project + - Press `F5` to run the project + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using CLU](https://docs.microsoft.com/azure/cognitive-services/language-service/conversational-language-understanding/overview) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/Startup.cs b/samples/csharp_dotnetcore/84.core-bot-clu/Startup.cs new file mode 100644 index 0000000000..b3f018932f --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/Startup.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.BotBuilderSamples.Bots; +using Microsoft.BotBuilderSamples.Dialogs; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.BotBuilderSamples +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddHttpClient().AddControllers().AddNewtonsoftJson(); + + // Create the Bot Framework Authentication to be used with the Bot Adapter. + services.AddSingleton(); + + // Create the Bot Adapter with error handling enabled. + services.AddSingleton(); + + // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) + services.AddSingleton(); + + // Create the User state. (Used in this bot's Dialog implementation.) + services.AddSingleton(); + + // Create the Conversation state. (Used by the Dialog system itself.) + services.AddSingleton(); + + // Register CLU recognizer + services.AddSingleton(); + + // Register the BookingDialog. + services.AddSingleton(); + + // The MainDialog that will be run by the bot. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient>(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles() + .UseStaticFiles() + .UseWebSockets() + .UseRouting() + .UseAuthorization() + .UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + // app.UseHttpsRedirection(); + } + } +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/appsettings.json b/samples/csharp_dotnetcore/84.core-bot-clu/appsettings.json new file mode 100644 index 0000000000..0deacc6fa1 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/appsettings.json @@ -0,0 +1,10 @@ +{ + "MicrosoftAppType": "", + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "MicrosoftAppTenantId": "", + "CluProjectName": "", + "CluDeploymentName": "", + "CluAPIKey": "", + "CluAPIHostName": "" +} diff --git a/samples/csharp_dotnetcore/84.core-bot-clu/wwwroot/default.html b/samples/csharp_dotnetcore/84.core-bot-clu/wwwroot/default.html new file mode 100644 index 0000000000..17a15414f8 --- /dev/null +++ b/samples/csharp_dotnetcore/84.core-bot-clu/wwwroot/default.html @@ -0,0 +1,425 @@ + + + + + + + Core Bot Sample + + + + + +
+
+
+
Core Bot Sample
+
+
+
+
+
Your bot is ready!
+
+ You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages. +
+ +
+ Visit + Azure + Bot Service + to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this: +
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + diff --git a/samples/csharp_dotnetcore/csharp_dotnetcore.sln b/samples/csharp_dotnetcore/csharp_dotnetcore.sln index 316adbf227..aba80b21cb 100644 --- a/samples/csharp_dotnetcore/csharp_dotnetcore.sln +++ b/samples/csharp_dotnetcore/csharp_dotnetcore.sln @@ -82,6 +82,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomQABot", "12.customQAB EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomQABotAllFeatures", "48.customQABot-all-features\CustomQABotAllFeatures.csproj", "{4C855F46-DCA3-4A69-9ED3-3B7491F91CF4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreBotCLU", "84.core-bot-clu\CoreBotCLU.csproj", "{60282FA2-C1B5-441B-8788-6761FD970D4B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -344,6 +346,14 @@ Global {4C855F46-DCA3-4A69-9ED3-3B7491F91CF4}.Release|Any CPU.Build.0 = Release|Any CPU {4C855F46-DCA3-4A69-9ED3-3B7491F91CF4}.Release|x64.ActiveCfg = Release|Any CPU {4C855F46-DCA3-4A69-9ED3-3B7491F91CF4}.Release|x64.Build.0 = Release|Any CPU + {60282FA2-C1B5-441B-8788-6761FD970D4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60282FA2-C1B5-441B-8788-6761FD970D4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60282FA2-C1B5-441B-8788-6761FD970D4B}.Debug|x64.ActiveCfg = Debug|Any CPU + {60282FA2-C1B5-441B-8788-6761FD970D4B}.Debug|x64.Build.0 = Debug|Any CPU + {60282FA2-C1B5-441B-8788-6761FD970D4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60282FA2-C1B5-441B-8788-6761FD970D4B}.Release|Any CPU.Build.0 = Release|Any CPU + {60282FA2-C1B5-441B-8788-6761FD970D4B}.Release|x64.ActiveCfg = Release|Any CPU + {60282FA2-C1B5-441B-8788-6761FD970D4B}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE