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:
Gabo Gilabert 2020-02-27 14:54:09 -05:00 коммит произвёл GitHub
Родитель 151777bfec
Коммит 1481157a7c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
34 изменённых файлов: 379 добавлений и 166 удалений

Просмотреть файл

@ -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; }
}
}