Skip to content

Commit

Permalink
Skill Samples (dotnet) (microsoft#2032)
Browse files Browse the repository at this point in the history
* First pass at DialogToDialog sample

* Updated nuget packages to latest nightly.

* Updated to 4.7 RC0.
Updated readmes
Reorganized MainDialog, SkillDialog and SkillDialogArgs.

* Updated readmes

* Adds SimpleBotToBot sample.
Updated readmes for DialotToDialog.

* Added a readme for the root folder.

* Addressed some of Jonathan's comments.
Addressed LUIS warning.

* Changed the conversation ID to avoid using hashcodes (the unique value will be given by the parent conversation ID).

* Changed how the skill conversation ID key gets generated.

* Fixed stylecop issues.

* Updated nuget references to RC1

* Updated code for partiy with JS

* Moved SimpleBotToBot in main samples solution.
Updated SimpleBotToBot to send an eoc on error with a status and text message.

* Rename skill adapter.
Updated OnError
Upated nuget packages to released versions.

* Readded skill sample that was lost in mege from master

* Updated readmes
Added sample claim validators for skill and parent.
Added status checks for skill call invocations.

* Updated links.

* Fixed some strings and comments.

* Update samples/csharp_dotnetcore/70.skills-simple-bot-to-bot/README.md

Co-Authored-By: Jonathan Fingold <[email protected]>

* Updated readme
  • Loading branch information
gabog authored and johnataylor committed Dec 18, 2019
1 parent 6ca6255 commit f1d9e1d
Show file tree
Hide file tree
Showing 74 changed files with 5,313 additions and 4 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,7 @@ generators/generator-botbuilder/.npmrc
/generators/generator-botbuilder/package-lock.json
package-lock.json
tsconfig.tsbuildinfo

# Visual Studio
appsettings.Development.json
.config/
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Builder.TraceExtensions;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.DialogRootBot.Middleware;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;

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}");

// Send a message to the user
var errorMessageText = "The bot encountered an error or bug.";
var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput);
await turnContext.SendActivityAsync(errorMessage);

errorMessageText = "To continue to run this bot, please fix the bot source code.";
errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput);
await turnContext.SendActivityAsync(errorMessage);

if (conversationState != null)
{
try
{
// Delete the conversationState for the current conversation to prevent the
// bot from getting stuck in a error-loop caused by being in a bad state.
// ConversationState should be thought of as similar to "cookie-state" in a Web pages.
await conversationState.DeleteAsync(turnContext);
}
catch (Exception e)
{
logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}");
}
}

// Send a trace activity, which will be displayed in the Bot Framework Emulator
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
};

Use(new LoggerMiddleware(logger));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Bot.Connector.Authentication;

namespace Microsoft.BotBuilderSamples.DialogRootBot.Authentication
{
/// <summary>
/// Sample claims validator that loads an allowed list from configuration if present
/// and checks that responses are coming from configured skills.
/// </summary>
public class AllowedCallersClaimsValidator : ClaimsValidator
{
private readonly List<string> _allowedSkills;

public AllowedCallersClaimsValidator(SkillsConfiguration skillsConfig)
{
if (skillsConfig == null)
{
throw new ArgumentNullException(nameof(skillsConfig));
}

// Load the appIds for the configured skills (we will only allow responses from skills we have configured).
_allowedSkills = (from skill in skillsConfig.Skills.Values select skill.AppId).ToList();
}

public override Task ValidateClaimsAsync(IList<Claim> claims)
{
if (SkillValidation.IsSkillClaim(claims))
{
// Check that the appId claim in the skill request is in the list of skills configured for this bot.
var appId = JwtTokenValidation.GetAppIdFromClaims(claims);
if (!_allowedSkills.Contains(appId))
{
throw new UnauthorizedAccessException($"Received a request from an application with an appID of \"{appId}\". To enable requests from this skill, add the skill to your configuration file.");
}
}

return Task.CompletedTask;
}
}
}
78 changes: 78 additions & 0 deletions experimental/skills/DialogToDialog/DialogRootBot/Bots/RootBot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;

namespace Microsoft.BotBuilderSamples.DialogRootBot.Bots
{
public class RootBot<T> : ActivityHandler
where T : Dialog
{
private readonly ConversationState _conversationState;
private readonly Dialog _mainDialog;

public RootBot(ConversationState conversationState, T mainDialog)
{
_conversationState = conversationState;
_mainDialog = mainDialog;
}

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
if (turnContext.Activity.Type != ActivityTypes.ConversationUpdate)
{
// Run the Dialog with the Activity.
await _mainDialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
else
{
await base.OnTurnAsync(turnContext, cancellationToken);
}

// Save any state changes that might have occurred during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}

protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in membersAdded)
{
// Greet anyone that was not the target (recipient) of this message.
// To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
if (member.Id != turnContext.Activity.Recipient.Id)
{
var welcomeCard = CreateAdaptiveCardAttachment();
var activity = MessageFactory.Attachment(welcomeCard);
await turnContext.SendActivityAsync(activity, cancellationToken);
await _mainDialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
}
}

// Load attachment from embedded resource.
private Attachment CreateAdaptiveCardAttachment()
{
var cardResourcePath = "Microsoft.BotBuilderSamples.DialogRootBot.Cards.welcomeCard.json";

using (var stream = GetType().Assembly.GetManifestResourceStream(cardResourcePath))
{
using (var reader = new StreamReader(stream))
{
var adaptiveCard = reader.ReadToEnd();
return new Attachment
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(adaptiveCard)
};
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"spacing": "Medium",
"size": "Medium",
"weight": "Bolder",
"text": "Welcome to the Dialog Skill Prototype!",
"wrap": true,
"maxLines": 0,
"color": "Accent"
},
{
"type": "TextBlock",
"size": "default",
"text": "Here are some things you can do:",
"wrap": true,
"maxLines": 0
},
{
"type": "TextBlock",
"size": "default",
"text": "Send a text message to the bot (message, single-turn)",
"wrap": true,
"weight": "Bolder",
"color": "Accent"
},
{
"type": "TextBlock",
"size": "default",
"text": "**m:some message** sends the text after **m:** directly to the skill and the skill does intent and entity recognition against LUIS.",
"wrap": true,
"spacing": "None"
},
{
"type": "TextBlock",
"size": "default",
"text": "Send a BookFlight event (event, multi-turn)",
"wrap": true,
"weight": "Bolder",
"color": "Accent"
},
{
"type": "TextBlock",
"size": "default",
"text": "**bookflight** sends an event to the skill with the name = 'BookFlight' to start a flight booking dialog.",
"wrap": true,
"spacing": "None"
},
{
"type": "TextBlock",
"size": "default",
"text": "Send a GetWeather invoke activity (invoke, single-turn)",
"wrap": true,
"weight": "Bolder",
"color": "Accent"
},
{
"type": "TextBlock",
"size": "default",
"text": "**getweather** sends an invoke activity to the skill with the name = 'GetWeather'. The skill replies right away (Invoke activities are Request/Response).",
"wrap": true,
"spacing": "None"
},
{
"type": "TextBlock",
"size": "default",
"text": "OAuthTest (event, multi-turn)",
"wrap": true,
"weight": "Bolder",
"color": "Accent"
},
{
"type": "TextBlock",
"size": "default",
"text": "**oauthtest** sends an event to the skill to start an OAuthCard dialog.",
"wrap": true,
"spacing": "None"
},
{
"type": "TextBlock",
"size": "default",
"text": "Send a message to the bot with values (message, single-turn)",
"wrap": true,
"weight": "Bolder",
"color": "Accent"
},
{
"type": "TextBlock",
"size": "default",
"text": "**mv:some message** sends some complex object in the Value property of the activity in addition to the message after **mv:**.",
"wrap": true,
"spacing": "None"
},
{
"type": "TextBlock",
"size": "default",
"text": "Canceling multi-turn conversations",
"wrap": true,
"weight": "Bolder",
"color": "Accent"
},
{
"type": "TextBlock",
"size": "default",
"text": "**abort** during a multi-turn dialog (i.e. BookFlight) sends an EndOfConversation activity to the skill to cancel the conversation.",
"wrap": true,
"spacing": "None"
},
{
"type": "TextBlock",
"size": "default",
"text": "**cancel** during a multi-turn dialog (i.e. OAuthTest) is recognized by the skill, it cancels its current dialog and sends an EndOfConversation activity to the parent.",
"wrap": true,
"spacing": "None"
},
{
"type": "TextBlock",
"size": "default",
"text": "Use the suggested actions bellow to test different parent to skill interactions.",
"wrap": true,
"maxLines": 0
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;

namespace Microsoft.BotBuilderSamples.DialogRootBot.Controllers
{
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
// achieved by specifying a more specific type for the bot constructor argument.
[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter _adapter;
private readonly IBot _bot;

public BotController(BotFrameworkHttpAdapter adapter, IBot bot)
{
_adapter = adapter;
_bot = bot;
}

[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await _adapter.ProcessAsync(Request, Response, _bot);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Builder.Skills;

namespace Microsoft.BotBuilderSamples.DialogRootBot.Controllers
{
/// <summary>
/// A controller that handles skill replies to the bot.
/// This example uses the <see cref="SkillHandler"/> that is registered as a <see cref="ChannelServiceHandler"/> in startup.cs.
/// </summary>
[ApiController]
[Route("api/skills")]
public class SkillController : ChannelServiceController
{
public SkillController(ChannelServiceHandler handler)
: base(handler)
{
}
}
}
Loading

0 comments on commit f1d9e1d

Please sign in to comment.