Updates to SkillDialog based on VA feedback. (#3458)
* Updates to SkillDialog based on VA feedback. * Updated samples to work with new changes, cleaned up namespace and added Error Handling code based on skill samples. * Updated MainDialog to show a remote skill cancelation from the a parent. * Remove FnTest project from the Skills solution that references Adapter projects that are not in scope
This commit is contained in:
Родитель
151777bfec
Коммит
1481157a7c
|
@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot.Authentication
|
||||
namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample claims validator that loads an allowed list from configuration if present
|
||||
|
|
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot.Bots
|
||||
namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot.Bots
|
||||
{
|
||||
public class EchoBot : ActivityHandler
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot.Controllers
|
||||
namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot.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
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.EchoSkillBot</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.EchoSkillBot</RootNamespace>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.DialogEchoSkillBot</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.DialogEchoSkillBot</RootNamespace>
|
||||
<UserSecretsId>a6aa19d1-4134-48c1-8970-8404e694e003</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
||||
namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"EchoSkillBot31": {
|
||||
"DialogEchoSkillBot": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
|
|
|
@ -10,7 +10,7 @@ using Microsoft.Bot.Schema;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
||||
namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
||||
{
|
||||
public class SkillAdapterWithErrorHandler : BotFrameworkHttpAdapter
|
||||
{
|
||||
|
|
|
@ -7,13 +7,13 @@ using Microsoft.Bot.Builder;
|
|||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.BotBuilderSamples.EchoSkillBot.Authentication;
|
||||
using Microsoft.BotBuilderSamples.EchoSkillBot.Bots;
|
||||
using Microsoft.BotBuilderSamples.DialogEchoSkillBot.Authentication;
|
||||
using Microsoft.BotBuilderSamples.DialogEchoSkillBot.Bots;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
||||
namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
|
|
|
@ -2,11 +2,17 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotBuilderSamples.DialogRootBot.Bots;
|
||||
using Microsoft.BotBuilderSamples.DialogRootBot.Dialogs;
|
||||
using Microsoft.BotBuilderSamples.DialogRootBot.Middleware;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -15,14 +21,39 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot
|
|||
{
|
||||
public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
|
||||
{
|
||||
public AdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger<AdapterWithErrorHandler> logger, ConversationState conversationState = null)
|
||||
: base(configuration, credentialProvider, authConfig, logger: logger)
|
||||
{
|
||||
OnTurnError = async (turnContext, exception) =>
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ConversationState _conversationState;
|
||||
private readonly ILogger _logger;
|
||||
private readonly SkillHttpClient _skillClient;
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
|
||||
public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState = null, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null)
|
||||
: base(configuration, logger)
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_conversationState = conversationState;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_skillClient = skillClient;
|
||||
_skillsConfig = skillsConfig;
|
||||
|
||||
OnTurnError = HandleTurnError;
|
||||
Use(new LoggerMiddleware(logger));
|
||||
}
|
||||
|
||||
private async Task HandleTurnError(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
_logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
|
||||
await SendErrorMessageAsync(turnContext, exception);
|
||||
await EndSkillConversationAsync(turnContext);
|
||||
await ClearConversationStateAsync(turnContext);
|
||||
}
|
||||
|
||||
private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send a message to the user
|
||||
var errorMessageText = "The bot encountered an error or bug.";
|
||||
var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput);
|
||||
|
@ -32,26 +63,63 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot
|
|||
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.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Use(new LoggerMiddleware(logger));
|
||||
private async Task EndSkillConversationAsync(ITurnContext turnContext)
|
||||
{
|
||||
if (_conversationState == null || _skillClient == null || _skillsConfig == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Inform the active skill that the conversation is ended so that it has
|
||||
// a chance to clean up.
|
||||
// Note: ActiveSkillPropertyName is set by the RooBot while messages are being
|
||||
// forwarded to a Skill.
|
||||
var activeSkill = await _conversationState.CreateProperty<BotFrameworkSkill>(MainDialog.ActiveSkillPropertyName).GetAsync(turnContext, () => null);
|
||||
if (activeSkill != null)
|
||||
{
|
||||
var botId = _configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
|
||||
var endOfConversation = Activity.CreateEndOfConversationActivity();
|
||||
endOfConversation.Code = "RootSkillError";
|
||||
endOfConversation.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true);
|
||||
|
||||
await _conversationState.SaveChangesAsync(turnContext, true);
|
||||
await _skillClient.PostActivityAsync(botId, activeSkill, _skillsConfig.SkillHostEndpoint, (Activity)endOfConversation, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught on attempting to send EndOfConversation : {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearConversationStateAsync(ITurnContext turnContext)
|
||||
{
|
||||
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 ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ using Microsoft.Bot.Builder.Dialogs;
|
|||
using Microsoft.Bot.Builder.Dialogs.Choices;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Builder.Skills.Dialogs;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
@ -24,6 +23,8 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
/// </summary>
|
||||
public class MainDialog : ComponentDialog
|
||||
{
|
||||
public static readonly string ActiveSkillPropertyName = $"{typeof(MainDialog).FullName}.ActiveSkillProperty";
|
||||
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty;
|
||||
private readonly string _selectedSkillKey = $"{typeof(MainDialog).FullName}.SelectedSkillKey";
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
|
||||
|
@ -52,15 +53,20 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
// ChoicePrompt to render available skills and skill actions
|
||||
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
|
||||
|
||||
// SkillDialog used to wrap interaction with the selected skill
|
||||
var skillDialogOptions = new SkillDialogOptions
|
||||
// Create SkillDialog instances for the configured skills
|
||||
foreach (var skillInfo in _skillsConfig.Skills.Values)
|
||||
{
|
||||
BotId = botId,
|
||||
ConversationIdFactory = conversationIdFactory,
|
||||
SkillClient = skillClient,
|
||||
SkillHostEndpoint = skillsConfig.SkillHostEndpoint
|
||||
};
|
||||
AddDialog(new SkillDialog(skillDialogOptions, conversationState));
|
||||
// SkillDialog used to wrap interaction with the selected skill
|
||||
var skillDialogOptions = new SkillDialogOptions
|
||||
{
|
||||
BotId = botId,
|
||||
ConversationIdFactory = conversationIdFactory,
|
||||
SkillClient = skillClient,
|
||||
SkillHostEndpoint = skillsConfig.SkillHostEndpoint,
|
||||
Skill = skillInfo
|
||||
};
|
||||
AddDialog(new SkillDialog(skillDialogOptions, conversationState, skillInfo.Id));
|
||||
}
|
||||
|
||||
// Main waterfall dialog for this bot
|
||||
var waterfallSteps = new WaterfallStep[]
|
||||
|
@ -72,17 +78,35 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
};
|
||||
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
|
||||
|
||||
// Create state property to track the active skill
|
||||
_activeSkillProperty = conversationState.CreateProperty<BotFrameworkSkill>(ActiveSkillPropertyName);
|
||||
|
||||
// The initial child Dialog to run.
|
||||
InitialDialogId = nameof(WaterfallDialog);
|
||||
}
|
||||
|
||||
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// This is an example on how to cancel a SkillDialog that is currently in progress from the parent bot
|
||||
var activeSkill = await _activeSkillProperty.GetAsync(innerDc.Context, () => null, cancellationToken);
|
||||
var activity = innerDc.Context.Activity;
|
||||
if (activeSkill != null && activity.Type == ActivityTypes.Message && activity.Text.Equals("abort", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
// Cancel all dialog when the user says abort.
|
||||
await innerDc.CancelAllDialogsAsync(cancellationToken);
|
||||
return await innerDc.ReplaceDialogAsync(InitialDialogId, "Canceled! \n\n What skill would you like to call?", cancellationToken);
|
||||
}
|
||||
|
||||
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
|
||||
}
|
||||
|
||||
// Render a prompt to select the skill to call.
|
||||
private async Task<DialogTurnResult> SelectSkillStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
// Create the PromptOptions from the skill configuration which contain the list of configured skills.
|
||||
var options = new PromptOptions
|
||||
{
|
||||
Prompt = MessageFactory.Text("What skill would you like to call?"),
|
||||
Prompt = MessageFactory.Text(stepContext.Options?.ToString() ?? "What skill would you like to call?"),
|
||||
RetryPrompt = MessageFactory.Text("That was not a valid choice, please select a valid skill."),
|
||||
Choices = _skillsConfig.Skills.Select(skill => new Choice(skill.Value.Id)).ToList()
|
||||
};
|
||||
|
@ -128,18 +152,14 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
skillActivity.Text = "Start echo skill";
|
||||
break;
|
||||
case "DialogSkillBot":
|
||||
skillActivity = GetDialogSkillBotActivity(((FoundChoice)stepContext.Result).Value);
|
||||
skillActivity = CreateDialogSkillBotActivity(((FoundChoice)stepContext.Result).Value);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unknown target skill id: {selectedSkill.Id}.");
|
||||
}
|
||||
|
||||
// Create the SkillDialogArgs
|
||||
var skillDialogArgs = new SkillDialogArgs
|
||||
{
|
||||
Skill = selectedSkill,
|
||||
Activity = skillActivity
|
||||
};
|
||||
var skillDialogArgs = new SkillDialogArgs { Activity = skillActivity };
|
||||
|
||||
// We are manually creating the activity to send to the skill, ensure we add the ChannelData and Properties
|
||||
// from the original activity so the skill gets them.
|
||||
|
@ -147,22 +167,33 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
skillDialogArgs.Activity.ChannelData = stepContext.Context.Activity.ChannelData;
|
||||
skillDialogArgs.Activity.Properties = stepContext.Context.Activity.Properties;
|
||||
|
||||
// Start the skillDialog with the arguments.
|
||||
return await stepContext.BeginDialogAsync(nameof(SkillDialog), skillDialogArgs, cancellationToken);
|
||||
// Save active skill in state
|
||||
await _activeSkillProperty.SetAsync(stepContext.Context, selectedSkill, cancellationToken);
|
||||
|
||||
// Start the skillDialog instance with the arguments.
|
||||
return await stepContext.BeginDialogAsync(selectedSkill.Id, skillDialogArgs, cancellationToken);
|
||||
}
|
||||
|
||||
// The SkillDialog has ended, render the results (if any) and restart MainDialog.
|
||||
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var activeSkill = await _activeSkillProperty.GetAsync(stepContext.Context, () => null, cancellationToken);
|
||||
|
||||
if (stepContext.Result != null)
|
||||
{
|
||||
var message = "Skill invocation complete.";
|
||||
var message = $"Skill \"{activeSkill.Id}\" invocation complete.";
|
||||
message += $" Result: {JsonConvert.SerializeObject(stepContext.Result)}";
|
||||
await stepContext.Context.SendActivityAsync(MessageFactory.Text(message, inputHint: InputHints.IgnoringInput), cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
// Clear the skill selected by the user.
|
||||
stepContext.Values[_selectedSkillKey] = null;
|
||||
|
||||
// Clear active skill in state
|
||||
await _activeSkillProperty.DeleteAsync(stepContext.Context, cancellationToken);
|
||||
|
||||
// Restart the main dialog with a different message the second time around
|
||||
return await stepContext.ReplaceDialogAsync(InitialDialogId, "What else can I do for you?", cancellationToken);
|
||||
return await stepContext.ReplaceDialogAsync(InitialDialogId, $"Done with \"{activeSkill.Id}\". \n\n What skill would you like to call?", cancellationToken);
|
||||
}
|
||||
|
||||
// Helper method to create Choice elements for the actions supported by the skill
|
||||
|
@ -190,11 +221,11 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
return choices;
|
||||
}
|
||||
|
||||
// Helper method to create the activity to be sent to the DialogSkillBot
|
||||
private Activity GetDialogSkillBotActivity(string selectedOption)
|
||||
// Helper method to create the activity to be sent to the DialogSkillBot using selected type and values
|
||||
private Activity CreateDialogSkillBotActivity(string selectedOption)
|
||||
{
|
||||
// Note: in a real bot, the dialogArgs will be created dynamically based on the conversation
|
||||
// and what each action requires, this code hardcodes the values to make things simpler.
|
||||
// and what each action requires, here we hardcode the values to make things simpler.
|
||||
|
||||
// Send a message activity to the skill.
|
||||
if (selectedOption.StartsWith("m:", StringComparison.CurrentCultureIgnoreCase))
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"DialogRootBot31": {
|
||||
"DialogRootBot": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"DialogSkillBot31": {
|
||||
"DialogSkillBot": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:1205/",
|
||||
|
|
|
@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot31.Authentication
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample claims validator that loads an allowed list from configuration if present
|
||||
|
|
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot31.Bots
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot.Bots
|
||||
{
|
||||
public class EchoBot : ActivityHandler
|
||||
{
|
||||
|
@ -26,5 +26,13 @@ namespace Microsoft.BotBuilderSamples.EchoSkillBot31.Bots
|
|||
await turnContext.SendActivityAsync(MessageFactory.Text("Say \"end\" or \"stop\" and I'll end the conversation and back to the parent."), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task OnEndOfConversationActivityAsync(ITurnContext<IEndOfConversationActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
// This will be called if the root bot is ending the conversation. Sending additional messages should be
|
||||
// avoided as the conversation may have been deleted.
|
||||
// Perform cleanup of resources if needed.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot31.Controllers
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot.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
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.EchoSkillBot31</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.EchoSkillBot31</RootNamespace>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.EchoSkillBot</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.EchoSkillBot</RootNamespace>
|
||||
<UserSecretsId>d3e58f1c-0841-4154-8a6e-c4dfc5ae3edf</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot31
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"EchoSkillBot31": {
|
||||
"EchoSkillBot": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"applicationUrl": "http://localhost:1205/",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ using Microsoft.Bot.Schema;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot31
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
||||
{
|
||||
public class SkillAdapterWithErrorHandler : BotFrameworkHttpAdapter
|
||||
{
|
||||
|
|
|
@ -7,13 +7,13 @@ using Microsoft.Bot.Builder;
|
|||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.BotBuilderSamples.EchoSkillBot31.Authentication;
|
||||
using Microsoft.BotBuilderSamples.EchoSkillBot31.Bots;
|
||||
using Microsoft.BotBuilderSamples.EchoSkillBot.Authentication;
|
||||
using Microsoft.BotBuilderSamples.EchoSkillBot.Bots;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot31
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
|
|
|
@ -2,25 +2,55 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotBuilderSamples.SimpleRootBot.Bots;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
||||
{
|
||||
public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
|
||||
{
|
||||
public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState = null)
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ConversationState _conversationState;
|
||||
private readonly ILogger _logger;
|
||||
private readonly SkillHttpClient _skillClient;
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
|
||||
public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState = null, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null)
|
||||
: base(configuration, logger)
|
||||
{
|
||||
OnTurnError = async (turnContext, exception) =>
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_conversationState = conversationState;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_skillClient = skillClient;
|
||||
_skillsConfig = skillsConfig;
|
||||
|
||||
OnTurnError = HandleTurnError;
|
||||
}
|
||||
|
||||
private async Task HandleTurnError(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
_logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
|
||||
await SendErrorMessageAsync(turnContext, exception);
|
||||
await EndSkillConversationAsync(turnContext);
|
||||
await ClearConversationStateAsync(turnContext);
|
||||
}
|
||||
|
||||
private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send a message to the user
|
||||
var errorMessageText = "The bot encountered an error or bug.";
|
||||
var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput);
|
||||
|
@ -30,24 +60,63 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot31
|
|||
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 ex)
|
||||
{
|
||||
logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// Send a trace activity, which will be displayed in the Bot Framework Emulator
|
||||
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EndSkillConversationAsync(ITurnContext turnContext)
|
||||
{
|
||||
if (_conversationState == null || _skillClient == null || _skillsConfig == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Inform the active skill that the conversation is ended so that it has
|
||||
// a chance to clean up.
|
||||
// Note: ActiveSkillPropertyName is set by the RooBot while messages are being
|
||||
// forwarded to a Skill.
|
||||
var activeSkill = await _conversationState.CreateProperty<BotFrameworkSkill>(RootBot.ActiveSkillPropertyName).GetAsync(turnContext, () => null);
|
||||
if (activeSkill != null)
|
||||
{
|
||||
var botId = _configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
|
||||
var endOfConversation = Activity.CreateEndOfConversationActivity();
|
||||
endOfConversation.Code = "RootSkillError";
|
||||
endOfConversation.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true);
|
||||
|
||||
await _conversationState.SaveChangesAsync(turnContext, true);
|
||||
await _skillClient.PostActivityAsync(botId, activeSkill, _skillsConfig.SkillHostEndpoint, (Activity)endOfConversation, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught on attempting to send EndOfConversation : {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearConversationStateAsync(ITurnContext turnContext)
|
||||
{
|
||||
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 ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ using System.Security.Claims;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31.Authentication
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample claims validator that loads an allowed list from configuration if present
|
||||
|
|
|
@ -14,10 +14,11 @@ using Microsoft.Bot.Schema;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31.Bots
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot.Bots
|
||||
{
|
||||
public class RootBot : ActivityHandler
|
||||
{
|
||||
public static readonly string ActiveSkillPropertyName = $"{typeof(RootBot).FullName}.ActiveSkillProperty";
|
||||
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty;
|
||||
private readonly string _botId;
|
||||
private readonly ConversationState _conversationState;
|
||||
|
@ -49,21 +50,32 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot31.Bots
|
|||
}
|
||||
|
||||
// Create state property to track the active skill
|
||||
_activeSkillProperty = conversationState.CreateProperty<BotFrameworkSkill>("activeSkillProperty");
|
||||
_activeSkillProperty = conversationState.CreateProperty<BotFrameworkSkill>(ActiveSkillPropertyName);
|
||||
}
|
||||
|
||||
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Forward all activities except EndOfConversation to the skill.
|
||||
if (turnContext.Activity.Type != ActivityTypes.EndOfConversation)
|
||||
{
|
||||
// Try to get the active skill
|
||||
var activeSkill = await _activeSkillProperty.GetAsync(turnContext, () => null, cancellationToken);
|
||||
if (activeSkill != null)
|
||||
{
|
||||
// Send the activity to the skill
|
||||
await SendToSkill(turnContext, activeSkill, cancellationToken);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnTurnAsync(turnContext, cancellationToken);
|
||||
|
||||
// Save any state changes that might have occured during the turn.
|
||||
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
|
||||
}
|
||||
|
||||
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
// Try to get the active skill
|
||||
var activeSkill = await _activeSkillProperty.GetAsync(turnContext, () => null, cancellationToken);
|
||||
|
||||
if (activeSkill != null)
|
||||
{
|
||||
// Send the activity to the skill
|
||||
await SendToSkill(turnContext, activeSkill, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
if (turnContext.Activity.Text.Contains("skill"))
|
||||
{
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Got it, connecting you to the skill..."), cancellationToken);
|
||||
|
@ -104,9 +116,6 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot31.Bots
|
|||
|
||||
// We are back at the root
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Back in the root bot. Say \"skill\" and I'll patch you through"), cancellationToken);
|
||||
|
||||
// Save conversation state
|
||||
await _conversationState.SaveChangesAsync(turnContext, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
|
||||
|
@ -120,14 +129,14 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot31.Bots
|
|||
}
|
||||
}
|
||||
|
||||
private async Task SendToSkill(ITurnContext<IMessageActivity> turnContext, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
|
||||
private async Task SendToSkill(ITurnContext turnContext, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
|
||||
{
|
||||
// NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
|
||||
// will have access to current accurate state.
|
||||
await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken);
|
||||
|
||||
// route the activity to the skill
|
||||
var response = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, (Activity)turnContext.Activity, cancellationToken);
|
||||
var response = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, turnContext.Activity, cancellationToken);
|
||||
|
||||
// Check response status
|
||||
if (!(response.Status >= 200 && response.Status <= 299))
|
||||
|
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31.Controllers
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot.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
|
||||
|
|
|
@ -6,7 +6,7 @@ using Microsoft.Bot.Builder;
|
|||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31.Controllers
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that handles skill replies to the bot.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.SimpleRootBot31</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.SimpleRootBot31</RootNamespace>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.SimpleRootBot</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.SimpleRootBot</RootNamespace>
|
||||
<UserSecretsId>a52d1fa1-0d90-42d7-bbfc-2d776e8b7804</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ using Microsoft.Bot.Builder.Skills;
|
|||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="SkillConversationIdFactory"/> that uses an in memory <see cref="ConcurrentDictionary{TKey,TValue}"/>
|
||||
|
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class that loads Skills information from configuration.
|
||||
|
|
|
@ -9,14 +9,13 @@ using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
|||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.BotBuilderSamples.SimpleRootBot31.Authentication;
|
||||
using Microsoft.BotBuilderSamples.SimpleRootBot31.Bots;
|
||||
using Microsoft.BotBuilderSamples.SimpleRootBot.Authentication;
|
||||
using Microsoft.BotBuilderSamples.SimpleRootBot.Bots;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot31
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
|
|
|
@ -68,8 +68,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Appli
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.TestBot", "tests\Microsoft.Bot.Builder.TestBot\Microsoft.Bot.Builder.TestBot.csproj", "{C113E0AE-5564-4389-BA39-183A8D574210}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.FunctionalTests", "FunctionalTests\Microsoft.Bot.Builder.FunctionalTests\Microsoft.Bot.Builder.FunctionalTests.csproj", "{B9DDC8CB-8EDF-4D98-913A-22F19E642223}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FunctionalTests", "FunctionalTests", "{8667F820-8ADA-4498-91AE-AE95DEE5227E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Testing", "libraries\Microsoft.Bot.Builder.Testing\Microsoft.Bot.Builder.Testing.csproj", "{060F070A-BBFA-490E-BE89-3844C857B771}"
|
||||
|
@ -396,14 +394,6 @@ Global
|
|||
{C113E0AE-5564-4389-BA39-183A8D574210}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C113E0AE-5564-4389-BA39-183A8D574210}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C113E0AE-5564-4389-BA39-183A8D574210}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Debug-Windows|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Debug-Windows|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Debug-Windows|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
@ -688,7 +678,6 @@ Global
|
|||
{3F4A0DD8-4D47-4B9C-939A-3146E68C84F7} = {0A0E26B0-7A46-4F1A-8BFE-9A763FDF6CF8}
|
||||
{D790A4BB-D8AC-4AAE-B3FE-0CF432CA8031} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
{C113E0AE-5564-4389-BA39-183A8D574210} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223} = {8667F820-8ADA-4498-91AE-AE95DEE5227E}
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
{76391566-9F22-4994-8B0F-02EFC0E9E228} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
|
|
|
@ -5,15 +5,13 @@ using System;
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Builder.Skills.Dialogs;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// A sample dialog that can wrap remote calls to a skill.
|
||||
/// A specialized <see cref="Dialog"/> that can wrap remote calls to a skill.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The options parameter in <see cref="BeginDialogAsync"/> must be a <see cref="SkillDialogArgs"/> instance
|
||||
|
@ -21,27 +19,22 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
/// </remarks>
|
||||
public class SkillDialog : Dialog
|
||||
{
|
||||
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty;
|
||||
private readonly ConversationState _conversationState;
|
||||
private readonly SkillDialogOptions _dialogOptions;
|
||||
|
||||
public SkillDialog(SkillDialogOptions dialogOptions, ConversationState conversationState)
|
||||
: base(nameof(SkillDialog))
|
||||
public SkillDialog(SkillDialogOptions dialogOptions, ConversationState conversationState, string dialogId = null)
|
||||
: base(dialogId)
|
||||
{
|
||||
_dialogOptions = dialogOptions ?? throw new ArgumentNullException(nameof(dialogOptions));
|
||||
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
|
||||
_activeSkillProperty = conversationState.CreateProperty<BotFrameworkSkill>($"{typeof(SkillDialog).FullName}.ActiveSkillProperty");
|
||||
}
|
||||
|
||||
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var dialogArgs = ValidateBeginDialogOptions(options);
|
||||
var dialogArgs = ValidateBeginDialogArgs(options);
|
||||
|
||||
await dc.Context.TraceActivityAsync($"{GetType().Name}.BeginDialogAsync()", label: $"Using activity of type: {dialogArgs.Activity.Type}", cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Store Skill information for this dialog instance
|
||||
await _activeSkillProperty.SetAsync(dc.Context, dialogArgs.Skill, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Create deep clone of the original activity to avoid altering it before forwarding it.
|
||||
var skillActivity = ObjectPath.Clone(dialogArgs.Activity);
|
||||
|
||||
|
@ -49,7 +42,7 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
skillActivity.ApplyConversationReference(dc.Context.Activity.GetConversationReference(), true);
|
||||
|
||||
// Send the activity to the skill.
|
||||
await SendToSkillAsync(dc, skillActivity, dialogArgs.Skill, cancellationToken).ConfigureAwait(false);
|
||||
await SendToSkillAsync(dc.Context, skillActivity, cancellationToken).ConfigureAwait(false);
|
||||
return EndOfTurn;
|
||||
}
|
||||
|
||||
|
@ -57,9 +50,6 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
{
|
||||
await dc.Context.TraceActivityAsync($"{GetType().Name}.ContinueDialogAsync()", label: $"ActivityType: {dc.Context.Activity.Type}", cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Retrieve the current skill information from ConversationState
|
||||
var skillInfo = await _activeSkillProperty.GetAsync(dc.Context, () => null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Handle EndOfConversation from the skill (this will be sent to the this dialog by the SkillHandler if received from the Skill)
|
||||
if (dc.Context.Activity.Type == ActivityTypes.EndOfConversation)
|
||||
{
|
||||
|
@ -71,13 +61,35 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
if (dc.Context.Activity.Type == ActivityTypes.Message || dc.Context.Activity.Type == ActivityTypes.Event)
|
||||
{
|
||||
// Just forward to the remote skill
|
||||
await SendToSkillAsync(dc, dc.Context.Activity, skillInfo, cancellationToken).ConfigureAwait(false);
|
||||
await SendToSkillAsync(dc.Context, dc.Context.Activity, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return EndOfTurn;
|
||||
}
|
||||
|
||||
private static SkillDialogArgs ValidateBeginDialogOptions(object options)
|
||||
public override async Task EndDialogAsync(ITurnContext turnContext, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Send of of conversation to the skill if the dialog has been cancelled.
|
||||
if (reason == DialogReason.CancelCalled || reason == DialogReason.ReplaceCalled)
|
||||
{
|
||||
await turnContext.TraceActivityAsync($"{GetType().Name}.EndDialogAsync()", label: $"ActivityType: {turnContext.Activity.Type}", cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
var activity = (Activity)Activity.CreateEndOfConversationActivity();
|
||||
|
||||
// Apply conversation reference and common properties from incoming activity before sending.
|
||||
activity.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true);
|
||||
activity.ChannelData = turnContext.Activity.ChannelData;
|
||||
activity.Properties = turnContext.Activity.Properties;
|
||||
|
||||
await SendToSkillAsync(turnContext, activity, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await base.EndDialogAsync(turnContext, instance, reason, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the the required properties are set in the options argument passed to the BeginDialog call.
|
||||
/// </summary>
|
||||
private static SkillDialogArgs ValidateBeginDialogArgs(object options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
|
@ -104,14 +116,15 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
return dialogArgs;
|
||||
}
|
||||
|
||||
private async Task SendToSkillAsync(DialogContext dc, Activity activity, BotFrameworkSkill skillInfo, CancellationToken cancellationToken)
|
||||
private async Task SendToSkillAsync(ITurnContext context, Activity activity, CancellationToken cancellationToken)
|
||||
{
|
||||
// Always save state before forwarding
|
||||
// (the dialog stack won't get updated with the skillDialog and things won't work if you don't)
|
||||
await _conversationState.SaveChangesAsync(dc.Context, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Create a conversationId to interact with the skill and send the activity
|
||||
var skillConversationId = await _dialogOptions.ConversationIdFactory.CreateSkillConversationIdAsync(activity.GetConversationReference(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Always save state before forwarding
|
||||
// (the dialog stack won't get updated with the skillDialog and things won't work if you don't)
|
||||
var skillInfo = _dialogOptions.Skill;
|
||||
await _conversationState.SaveChangesAsync(context, true, cancellationToken).ConfigureAwait(false);
|
||||
var response = await _dialogOptions.SkillClient.PostActivityAsync(_dialogOptions.BotId, skillInfo.AppId, skillInfo.SkillEndpoint, _dialogOptions.SkillHostEndpoint, skillConversationId, activity, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Inspect the skill response status
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs
|
||||
|
@ -11,14 +10,6 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
/// </summary>
|
||||
public class SkillDialogArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="BotFrameworkSkill"/> that the dialog will call.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="BotFrameworkSkill"/> that the dialog will call.
|
||||
/// </value>
|
||||
public BotFrameworkSkill Skill { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Activity"/> to send to the skill.
|
||||
/// </summary>
|
||||
|
|
|
@ -2,17 +2,53 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Skills.Dialogs
|
||||
namespace Microsoft.Bot.Builder.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the options that will be used to execute a <see cref="SkillDialog"/>.
|
||||
/// </summary>
|
||||
public class SkillDialogOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the the Microsoft app ID of the bot calling the skill.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The the Microsoft app ID of the bot calling the skill.
|
||||
/// </value>
|
||||
public string BotId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="BotFrameworkClient"/> used to call the remote skill.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="BotFrameworkClient"/> used to call the remote skill.
|
||||
/// </value>
|
||||
public BotFrameworkClient SkillClient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback Url for the skill host.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The callback Url for the skill host.
|
||||
/// </value>
|
||||
public Uri SkillHostEndpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="BotFrameworkSkill"/> that the dialog will call.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="BotFrameworkSkill"/> that the dialog will call.
|
||||
/// </value>
|
||||
public BotFrameworkSkill Skill { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an instance of a <see cref="SkillConversationIdFactoryBase"/> used to generate conversation IDs for interacting with the skill.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// An instance of a <see cref="SkillConversationIdFactoryBase"/> used to generate conversation IDs for interacting with the skill.
|
||||
/// </value>
|
||||
public SkillConversationIdFactoryBase ConversationIdFactory { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче