Skip to content

Instantly share code, notes, and snippets.

@vcardins
Last active May 8, 2018 23:56
Show Gist options
  • Save vcardins/23a9f363ef1eeb7a3414790193148881 to your computer and use it in GitHub Desktop.
Save vcardins/23a9f363ef1eeb7a3414790193148881 to your computer and use it in GitHub Desktop.
Multiple contexts BOT sample
using System.Collections.Generic;
namespace MyBot.Api.Bot
{
public class GlobalState : Dictionary<string, object>
{
private const string UpdateProfileKey = "UpdateProfile";
private const string CreateProjectKey = "CreateProject";
public GlobalState()
{
this[UpdateProfileKey] = new UpdateProfileState();
this[CreateProjectKey] = new CreateProjectState();
}
public UpdateProfileState UpdateProfile
{
get { return (UpdateProfileState)this[UpdateProfileKey]; }
set { this[UpdateProfileKey] = value; }
}
public CreateProjectState CreateProject
{
get { return (CreateProjectState)this[CreateProjectKey]; }
set { this[CreateProjectKey] = value; }
}
public string PhoneNumber
{
get { return (string)this[PhoneNumberKey]; }
set { this[PhoneNumberKey] = value; }
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MyBot.Core.Interfaces;
using Microsoft.Bot;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Core.Extensions;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Prompts;
using Microsoft.Bot.Builder.Prompts.Choices;
using Microsoft.Bot.Schema;
using Microsoft.Recognizers.Text;
using Newtonsoft.Json;
using TextPrompt = Microsoft.Bot.Builder.Dialogs.TextPrompt;
using ChoicePrompt = Microsoft.Bot.Builder.Dialogs.ChoicePrompt;
namespace MyBot.Api.Bot
{
public static class PromptStep
{
public const string GatherInfo = "gatherInfo";
public const string CardPrompt = "cardPrompt";
public const string CardSelector = "cardSelector";
}
public static class MenuOptions
{
public static KeyValuePair<string, string> CreateProject = new KeyValuePair<string, string>("1", "Create Project");
public static KeyValuePair<string, string> UpdateProfile = new KeyValuePair<string, string>("2", "Update Profile");
public static KeyValuePair<string, string> Help = new KeyValuePair<string, string>("3", "Help");
}
public partial class MyBot : IBot
{
private readonly DialogSet _dialogs;
private readonly DialogSet _updateProfileDialogs;
private readonly ChoicePromptOptions _cardOptions;
private GlobalState GlobalState;
private ITurnContext _globalContext;
private IUserProfileService _userProfileService;
private ILookupService _lookupService;
public MyBot(
IUserProfileService userProfileService,
ILookupService lookupService
)
{
_lookupService = lookupService;
_userProfileService = userProfileService;
_dialogs = new DialogSet();
// Choice prompt with list style to show card types
var cardPrompt = new ChoicePrompt(Culture.English)
{
Style = ListStyle.SuggestedAction
};
_cardOptions = GenerateOptions();
// Register the card prompt
_dialogs.Add(PromptStep.CardPrompt, cardPrompt);
// Create a dialog waterfall for prompting the user
_dialogs.Add(PromptStep.CardSelector, new WaterfallStep[] {
ChoiceCardStepAsync,
ShowMenuOptions
});
_updateProfileDialogs = new DialogSet();
// Create prompt for name with string length validation
_dialogs.Add(UpdateProfilePromptStep.FirstNamePrompt, new TextPrompt(FirstNameValidator));
_dialogs.Add(UpdateProfilePromptStep.LastNamePrompt, new TextPrompt(LastNameValidator));
_dialogs.Add(UpdateProfilePromptStep.PhoneNumberPrompt, new TextPrompt(PhoneNumberValidator));
// Create a dialog waterfall for prompting the user
_dialogs.Add(UpdateProfilePromptStep.GatherUserProfile, new WaterfallStep[] {
AskFirstNameStep,
AskLastNameStep,
AskPhoneNumberStep,
GatherUserProfileStep
});
// TODO: Add dialogs for other options
}
public async Task OnTurn(ITurnContext context)
{
_globalContext = context;
GlobalState = context.GetConversationState<GlobalState>();
var dialogContext = _dialogs.CreateContext(context, GlobalState);
switch (context.Activity.Type)
{
case ActivityTypes.ConversationUpdate:
var from = dialogContext.Context.Activity.From;
var user = await _userProfileService.GetUserByUsernameAsync(from.Id);
var userDisplayName = user != null ? user.DisplayName : from.Name;
var message = user != null
? "welcome back, hope you're doing well"
: "it seems you're new around here. Welcome!";
await dialogContext.Context.SendActivity($"Hello {userDisplayName}, {message}. How can I help you today?");
break;
case ActivityTypes.Message:
var activeDialog = dialogContext.ActiveDialog;
await dialogContext.Continue();
//Console.WriteLine(activeDialog?.Id);
if (!context.Responded)
{
await dialogContext.Begin(PromptStep.CardSelector); // UpdateProfilePromptStep.GatherUserProfile
}
break;
case ActivityTypes.DeleteUserData:
// Implement user deletion here
// If we handle user deletion, return a real message
break;
case ActivityTypes.ContactRelationUpdate:
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
break;
case ActivityTypes.Typing:
// Handle knowing that the user is typing
break;
case ActivityTypes.Ping:
await context.SendActivity("Pong");
break;
default:
break;
}
}
// Create our prompt's choices
private ChoicePromptOptions GenerateOptions()
{
return new ChoicePromptOptions()
{
Choices = new List<Choice>()
{
new Choice()
{
Value = MenuOptions.CreateProject.Value,
Synonyms = new List<string>() { MenuOptions.UpdateProfile.Key, MenuOptions.CreateProject.Value.ToLower() }
},
new Choice()
{
Value = MenuOptions.UpdateProfile.Value,
Synonyms = new List<string>() { MenuOptions.UpdateProfile.Key, MenuOptions.UpdateProfile.Value.ToLower() }
},
new Choice()
{
Value = MenuOptions.Help.Value,
Synonyms = new List<string>() { MenuOptions.Help.Key, MenuOptions.Help.Value.ToLower() }
},
}
};
}
private async Task ChoiceCardStepAsync(DialogContext dialogContext, object result, SkipStepFunction next)
{
await dialogContext.Prompt(PromptStep.CardPrompt, "Which option would you like to choose?", _cardOptions);
}
private async Task ShowMenuOptions(DialogContext dialogContext, object result, SkipStepFunction next)
{
var selectedCard = (result as ChoiceResult).Value.Value;
var activity = dialogContext.Context.Activity;
switch (selectedCard)
{
case "Update Profile":
//var updateProfileDialogContext = _updateProfileDialogs.CreateContext(_globalContext, GlobalState.UpdateProfile);
//await updateProfileDialogContext.Continue();
await dialogContext.Replace(UpdateProfilePromptStep.GatherUserProfile);
break;
case "Help":
await dialogContext.Context.SendActivity(CreateResponse(activity, await HandleHelp()));
break;
default:
await dialogContext.Context.SendActivity(CreateResponse(activity, await HandleHelp()));
break;
}
await dialogContext.End();
}
}
}
using System.Threading.Tasks;
using MyBot.Core.ViewModels;
using Microsoft.Bot;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Core.Extensions;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Prompts;
namespace MyBot.Api.Bot
{
public static class UpdateProfilePromptStep
{
public const string GatherUserProfile = "gatherUserProfile";
public const string FirstNamePrompt = "firstName";
public const string LastNamePrompt = "lastName";
public const string PhoneNumberPrompt = "phoneNumber";
}
public partial class MyBot : IBot
{
private async Task FirstNameValidator(ITurnContext context, TextResult result)
{
if (result.Value.Length <= 3)
{
result.Status = PromptStatus.NotRecognized;
await context.SendActivity("Your name should be at least 3 characters long.");
}
}
private async Task LastNameValidator(ITurnContext context, TextResult result)
{
if (result.Value.Length <= 3)
{
result.Status = PromptStatus.NotRecognized;
await context.SendActivity("Your name should be at least 3 characters long.");
}
}
private async Task PhoneNumberValidator(ITurnContext context, TextResult result)
{
if (result.Value.Length != 10)
{
result.Status = PromptStatus.NotRecognized;
await context.SendActivity("Your phone number should be between 10 chars long.");
}
}
private async Task AskFirstNameStep(DialogContext dialogContext, object result, SkipStepFunction next)
{
await dialogContext.Prompt(UpdateProfilePromptStep.FirstNamePrompt, "What is your first name?");
}
private async Task AskLastNameStep(DialogContext dialogContext, object result, SkipStepFunction next)
{
var state = dialogContext.Context.GetConversationState<GlobalState>();
state.UpdateProfile.FirstName = (result as TextResult).Value;
await dialogContext.Prompt(UpdateProfilePromptStep.LastNamePrompt, "What is your last name?");
}
private async Task AskPhoneNumberStep(DialogContext dialogContext, object result, SkipStepFunction next)
{
var state = dialogContext.Context.GetConversationState<GlobalState>();
state.UpdateProfile.LastName = (result as TextResult).Value;
await dialogContext.Prompt(UpdateProfilePromptStep.PhoneNumberPrompt, "What is your phone number?");
}
private async Task GatherUserProfileStep(DialogContext dialogContext, object result, SkipStepFunction next)
{
var state = dialogContext.Context.GetConversationState<GlobalState>();
state.UpdateProfile.PhoneNumber = (result as TextResult).Value;
var from = dialogContext.Context.Activity.From;
var success = await _userProfileService.AddOrUpdate(
new UserInput
{
Username = from.Id,
DisplayName = state.UpdateProfile.FullName ?? from.Name,
PhoneNumber = state.UpdateProfile.PhoneNumber
}
);
if (success) {
await dialogContext.Context.SendActivity($"Your full name is {state.UpdateProfile.FullName} and your phone number is {state.UpdateProfile.PhoneNumber}");
} else {
await dialogContext.Context.SendActivity("An erorr has occured");
}
await dialogContext.End();
}
}
}
...
var credentialProvider = new SimpleCredentialProvider(
Configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value,
Configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppPasswordKey)?.Value
);
services.AddBot<MyBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
// The CatchExceptionMiddleware provides a top-level exception handler for your bot.
// Any exceptions thrown by other Middleware, or by your OnTurn method, will be
// caught here. To facillitate debugging, the exception is sent out, via Trace,
// to the emulator. Trace activities are NOT displayed to users, so in addition
// an "Ooops" message is sent.
options.Middleware.Add(new CatchExceptionMiddleware<Exception>(async (context, exception) =>
{
await context.TraceActivity("EchoBot Exception", exception);
await context.SendActivity(exception.Message); // "Sorry, it looks like something went wrong!"
}));
// The Memory Storage used here is for local bot debugging only. When the bot
// is restarted, anything stored in memory will be gone.
IStorage dataStore = new MemoryStorage();
// The File data store, shown here, is suitable for bots that run on
// a single machine and need durable state across application restarts.
// IStorage dataStore = new FileStorage(System.IO.Path.GetTempPath());
// For production bots use the Azure Table Store, Azure Blob, or
// Azure CosmosDB storage provides, as seen below. To include any of
// the Azure based storage providers, add the Microsoft.Bot.Builder.Azure
// Nuget package to your solution. That package is found at:
// https://www.nuget.org/packages/Microsoft.Bot.Builder.Azure/
// IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureTableStorage("AzureTablesConnectionString", "TableName");
// IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureBlobStorage("AzureBlobConnectionString", "containerName");
options.Middleware.Add(new ConversationState<GlobalState>(dataStore));
});
...
using System.Collections.Generic;
namespace MyBot.Api.Bot
{
public class UpdateProfileState : Dictionary<string, object>
{
private const string FirstNameKey = "firstName";
private const string LastNameKey = "lastName";
private const string PhoneNumberKey = "phoneNumber";
public UpdateProfileState()
{
this[FirstNameKey] = null;
this[LastNameKey] = null;
this[PhoneNumberKey] = null;
}
public string FirstName
{
get { return (string)this[FirstNameKey]; }
set { this[FirstNameKey] = value; }
}
public string LastName
{
get { return (string)this[LastNameKey]; }
set { this[LastNameKey] = value; }
}
public string PhoneNumber
{
get { return (string)this[PhoneNumberKey]; }
set { this[PhoneNumberKey] = value; }
}
public string FullName
{
get { return string.Format("{0} {1}", (string)this[FirstNameKey], (string)this[LastNameKey]); }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment