WaterfallDialog, fire the first step without the user sending any message. C# - c#

I want the first step of the WaterfallDialog to prompt when the class is called. Right now to only do this is to use the OnMembersAddedAsync method which sends like a welcome message. Which is not I needed.
I want the StartConversation to automatically fire when the class is called.
public class HomeOfficeDialogV3 : ComponentDialog
{
private readonly StateAccessor _StateAccessor;
private readonly DirectlineApi _TokenReq;
private readonly IndicatorDelay _IndicatorDelay;
private UserDataExtractedDto _UserDataExtractedDto;
public HomeOfficeDialogV3(StateAccessor userState)
{
_StateAccessor = userState;
_TokenReq = new DirectlineApi();
_IndicatorDelay = new IndicatorDelay();
var waterfallSteps = new WaterfallStep[]
{
StartConversation,
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)) { Style = ListStyle.SuggestedAction });
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>)));
InitialDialogId = nameof(WaterfallDialog);
}
public HomeOfficeDialogV3(UserDataExtractedDto userDataExtractedDto)
{
_UserDataExtractedDto = userDataExtractedDto;
}
public async Task<DialogTurnResult> StartConversation(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await _IndicatorDelay.ShowTypingIndicator(stepContext.Context, 1400);
return await stepContext.PromptAsync(nameof(ChoicePrompt),
new PromptOptions {
Prompt = MessageFactory.Text($"Did you know your office space can affect your well-being?"),
Choices = ChoiceFactory.ToChoices(new List<string> { "Hello Buddy 😊" })
}
);
}
}
public MainDialog(IConfiguration configuration, StateAccessor userState)
{
_StateAccessor = userState;
_TokenReq = new DirectlineApi();
_DialogsTurn = new DialogsTurn();
private DialogCurrentDayId _DialogCurrentDayId;
var waterfallSteps = new WaterfallStep[]
{
DialogsTurn
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new WelcomeDiloagV1(userState));
AddDialog(new ExerciseDialogV2(userState));
AddDialog(new HomeOfficeDialogV3(userState));
InitialDialogId = nameof(WaterfallDialog);
}
public MainDialog(DialogCurrentDayId dialogCurrentDayId)
{
_DialogCurrentDayId = dialogCurrentDayId;
}
private async Task<DialogTurnResult> DialogsTurn(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var dialogTurnID = Convert.ToInt32(UserDataExtractedDto.DialogTurnID);
var dialogEnum = Enum.GetValues(typeof(DialogEnumerator)).Cast<DialogEnumerator>();
var currentDialog = String.Empty;
foreach(DialogEnumerator dialog in dialogEnum)
{
if (dialogTurnID > (int)dialog)
{
dialogTurnID = dialogEnum.Count();
}
if ((int)dialog == dialogTurnID)
{
currentDialog = dialog.ToString();
}
}
return await stepContext.BeginDialogAsync(currentDialog, null, cancellationToken);
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<MainDialog>();
services.AddTransient<IBot, MainBot<MainDialog>>();
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
var token = turnContext.Activity.From.Name;
if (!String.IsNullOrEmpty(token))
{
var ConversationExistingId = await _StateAccessor.existingConversation.GetAsync(turnContext, () => new ExistingConversation(), cancellationToken);
if (_IsTest)
{
_UserDataFromReactWebChat.Token = _TokenTest;
_UserDataFromReactWebChat.UserId = _IdTest;
}
else
{
_UserDataFromReactWebChat.Token = turnContext.Activity.From.Name;
_UserDataFromReactWebChat.UserId = Int32.Parse(turnContext.Activity.From.Id);
}
var getDialogID = new DialogsTurn(_UserDataFromReactWebChat);
var initializeDialogsById = await getDialogID.GetNextDialog();
if (initializeDialogsById != null){
new MainDialog(initializeDialogsById);
}
await _IndicatorDelay.ShowTypingIndicator(turnContext, 5500);
var isGranted = String.Empty;
var userAllData = new UsersAllData(_UserDataFromReactWebChat);
var usersInfoFromWebApp = await userAllData.GetUsersInfoFromWebApp();
foreach (var i in usersInfoFromWebApp)
{
if(!String.IsNullOrEmpty(i.IsGranted))
{
isGranted = i.IsGranted.ToString();
}
new WelcomeDiloagV1(i);
new ExerciseDialogV2(i);
new HomeOfficeDialogV3(i);
//break;
}
if (isGranted == "true")
{
if (string.IsNullOrEmpty(ConversationExistingId.ConverstationId))
{
ConversationExistingId.ConverstationId = turnContext.Activity.Conversation.Id;
await _StateAccessor.existingConversation.SetAsync(turnContext, ConversationExistingId, cancellationToken);
await _StateAccessor.existingConversationState.SaveChangesAsync(turnContext);
}
else
{
var Conversation_id = ConversationExistingId.ConverstationId;
var TokenGenerated = _UserDataFromReactWebChat.Token;
}
}
else if (isGranted == "false")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Unathorized requests."), cancellationToken);
}
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Oops! Something went wrong."), cancellationToken);
}
}
}
}
I have multiple steps but I cut it out for this example.

If your bot is based on the document that you linked to then it will access the dialog stack from OnMessageActivityAsync using Dialog.RunAsync. You can do the same thing in OnMembersAddedAsync.

Related

this.Clients are disposed SignalR

At the time of the user's connection, everything is going well. When I have a product update (it becomes available for sale), the OnProductStartSales event is called.
But there is one problem, the message does not come to the client because the list of clients in the hub is disposed.
Here is the code of my hub.
public class SalesHub : Hub
{
private readonly ProductDatabaseListener _listener;
public SalesHub(ProductDatabaseListener listener)
{
_listener = listener ?? throw new ArgumentNullException(nameof(listener));
_listener.OnProductStartSales += (s, p) => ProductStartSales(p);
_listener.OnProductDataChanged += (s, p) => ProductDataChanged(p);
}
public async Task ListenProduct(string productId)
{
await this.Groups.AddToGroupAsync(Context.ConnectionId, productId);
}
private async Task ProductStartSales(Product product)
{
await this.Clients.Group(product.Id).SendAsync("StartSales", product.Id);
// await this.Clients.All.SendAsync("StartSales", product.Id);
}
private async Task ProductDataChanged(Product product)
{
await this.Clients.Group(product.Id).SendAsync("DataChanged", product);
}
}
Here is the code of listener.
public class ProductDatabaseListener
{
private readonly IRepository<Product> _repository;
private readonly object _locker = new object();
public ProductDatabaseListener(IServiceProvider serviceProvider)
{
using (var scope = serviceProvider.CreateScope())
{
_repository = scope.ServiceProvider.GetRequiredService<IRepository<Product>>() ?? throw new ArgumentNullException(nameof(_repository));
}
}
public event EventHandler<Product> OnProductStartSales;
public event EventHandler<Product> OnProductDataChanged;
// Need better performance...
public async Task ListenMongo()
{
while (true)
{
var products = await _repository.GetRange(0, int.MaxValue);
var date = DateTime.Now;
List<Task> tasks = new List<Task>();
foreach (var product in products)
{
if (product.IsSalesStart)
{
continue;
}
if (product.StartOfSales <= date)
{
product.IsSalesStart = true;
OnProductStartSales?.Invoke(this, product);
tasks.Add(_repository.Update(product));
}
}
Task.WaitAll(tasks.ToArray());
await Task.Delay(1000);
}
}
}
Here is the client code
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/salesHub").build();
connection.on("ReceiveMessage", function (id) {
var li = document.createElement("li")
document.getElementById("fromHub").appendChild(li)
li.textContent = id;
});
connection.on("startSales", function (id) {
var productId = document.getElementById("objectId").getAttribute("value");
if (productId == id) {
var button = document.getElementById("buy")
button.hidden = false
}
});
connection.logging = true;
connection.start().then(function () {
var productId = document.getElementById("objectId").getAttribute("value");
connection.invoke("ListenProduct", productId).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
}).catch(function (err) {
return console.error(err.toString());
});

How to pass values from StepContext to Promptvalidatorcontext in bot waterfall dialog model?

I'm new to Bot Framework, if i made any mistake, how to correct it?
A simple scenario of my Bot which is used for flight booking, bot gets input from user for from city, to city, departure date, return date. After receiving return date, i have to validate whether the given date is greater than departure date.
How to pass values from StepContext to Promptvalidatorcontext?
public class Travel_FlightBookingDialog : ComponentDialog
{
public Travel_FlightBookingDialog(string dialogId) : base(nameof(Travel_FlightBookingDialog))
{
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new ConfirmPrompt("confirmPrompt"));
AddDialog(new TextPrompt("FROMCITY", FromCityValidationAsync));
AddDialog(new TextPrompt("DEPARTUREDATE", DepartureValidationAsync));
AddDialog(new TextPrompt("ARRIVALDATE", ArrivalValidationAsync));
AddDialog(new TextPrompt("PAXCOUNT", PaxCountValidationAsync));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
BOOKINGTYPE,
FROMCITY,
TOCITY,
DEPARTUREDATE,
ARRIVALDATE,
PAXCOUNT,
CLASSOFBOOKING,
DIRECTORCONN,
LCCORFSC,
CONFIRM,
FINAL
}));
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<bool> DepartureValidationAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
var DepartureDate = promptContext.Recognized.Value;
string valifinfo = ValidateDate(DepartureDate);
if (valifinfo == "")
{
return true;
}
else
{
await promptContext.Context.SendActivityAsync(valifinfo);
return false;
}
}
private async Task<bool> ArrivalValidationAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
**//HOW DO WE GET THE VALUE OF DEPARTURE DATE HERE BECAUSE
//HERE NEED TO VALIDATE - RETURN DATE SHOULD BE GREATER THAN DEPARTURE DATE**
}
private async Task<bool> FromCityValidationAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
var FromcityValidation = promptContext.Recognized.Value;
var count = Convert.ToString(promptContext.Recognized.Value).Length;
List<string> range;
if (count != 3)
{
range = IataHelper1.findAirport("", FromcityValidation);
var strlist = "";
foreach (string item in range)
{
if (item != "NODATA")
{
strlist += item + Environment.NewLine;
}
}
if (range.Count > 0)
{
if (range.Count == 1)
{
return true;
}
else if (range.Count > 1)
{
await promptContext.Context.SendActivityAsync($"Kindly Choose any one Airport from suggestion List :{Environment.NewLine} {strlist}");
return false;
}
}
else
{
await promptContext.Context.SendActivityAsync("Please Enter valid City name.....!!!!!");
return false;
}
}
else
{
range = IataHelper1.findAirport(FromcityValidation, "");
if (range != null)
{
foreach (string item in range)
{
if (item != "NODATA")
{
///return Task.FromResult(true);
return true;
}
}
}
else
{
await promptContext.Context.SendActivityAsync("Please Enter valid City Code.....!!!!!");
return false;
}
}
return false;
}
private async Task<DialogTurnResult> BOOKINGTYPE(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var pmoptions = new PromptOptions
{
Prompt = MessageFactory.Text($"Please Choose Booking Type :"),
Choices = GetBookingType()
};
return await stepContext.PromptAsync(nameof(ChoicePrompt), pmoptions, cancellationToken);
}
private async Task<DialogTurnResult> FROMCITY(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["BOOKINGTYPE"] = stepContext.Result;
var promptFromCity = stepContext.PromptAsync("FROMCITY", new PromptOptions()
{
Prompt = MessageFactory.Text($"Please enter Departure City Code or City Name (Eg: DEL or Delhi)"),
RetryPrompt = MessageFactory.Text("Please enter the valid City Code or City Name.....")
}, cancellationToken);
return await promptFromCity;
}
private async Task<DialogTurnResult> TOCITY(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["FROMCITY"] = stepContext.Result;
var promptToCity = stepContext.PromptAsync("FROMCITY", new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter Arrival City Code or City Name (Eg: MAA or Chennai)"),
RetryPrompt = MessageFactory.Text("Please enter the valid City Code or City Name.....")
}, cancellationToken);
return await promptToCity;
}
private async Task<DialogTurnResult> DEPARTUREDATE(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["TOCITY"] = stepContext.Result;
var promptDepartureDate = stepContext.PromptAsync("DEPARTUREDATE", new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter Departure Date : (Eg: DD-MM-YYYY format)..."),
RetryPrompt = MessageFactory.Text("Please enter the valid Date.....")
}, cancellationToken);
return await promptDepartureDate;
}
private async Task<DialogTurnResult> ARRIVALDATE(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["DEPARTUREDATE"] = stepContext.Result;
var bookingtype = (FoundChoice)stepContext.Values["BOOKINGTYPE"];
if (bookingtype.Value == "ONEWAY")
{
return await stepContext.NextAsync(null, cancellationToken);
}
var promptArrivalDate = stepContext.PromptAsync("ARRIVALDATE", new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter Arrival Date in case of Roundtrip : (Eg: DD-MM-YYYY format)..."),
RetryPrompt = MessageFactory.Text("Please enter the valid Date.....")
}, cancellationToken);
return await promptArrivalDate;
}
private async Task<DialogTurnResult> PAXCOUNT(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["ARRIVALDATE"] = stepContext.Result;
var promptPaxCount = stepContext.PromptAsync("PAXCOUNT", new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter No.Of Passengers : (Eg: AdultCount,ChildCount,InfantCount)..."),
}, cancellationToken);
return await promptPaxCount;
}
public string ValidateDate(string Date)
{
DateTime dateTimeObj = DateTime.ParseExact(Date, "dd-MM-yyyy", CultureInfo.InvariantCulture);
if (Date != "" && dateTimeObj < DateTime.Today)
{
return "Date should be Future or equal To Today Date";
}
return "";
}
}
You should use turnContext to pass information from stepContext to promptContext.
promptContext.context.turnState. =

Botframework resume dialog not called

I have a problem with my component dialogs in botframework V4.
I'm using a root dialog which contains a Waterfalldialog and an Setupdialog. The initial dialog is the Setupdialog. Like so:
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
In the setup dialog I'm asking for some value. If the setup dialogs continues I check the value and if its like I wanted, I end the dialog.
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
If I end the dialog like this, shouldn't be ResumeDialog be called in the RootDialog?
Here the whole Dialogs:
public class RootDialog : ComponentDialog
{
private int recursions;
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
public async Task<DialogTurnResult> ProcessStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Process step in root dialog")
});
if(Dialogs.Find(nameof(RecursiveDialog)) == null)
{
AddDialog(new RecursiveDialog(new DialogSet(), recursions));
}
if (recursions > 0)
{
return await stepContext.BeginDialogAsync(nameof(RecursiveDialog));
}
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Recursion lower or eqaul 0")
});
}
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
var dialogContext = CreateChildContext(outerDc);
await dialogContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Begin root dialog")
});
return await base.BeginDialogAsync(outerDc, options, cancellationToken);
}
public override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
if(innerDc.ActiveDialog != null)
{
return await innerDc.ContinueDialogAsync();
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
{
recursions = Convert.ToInt32(result);
await outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text($"Resume root dialog with recursion value {recursions}")
});
return await outerDc.BeginDialogAsync(nameof(WaterfallDialog));
}
}
SetupDialog:
public class SetupDialog : ComponentDialog
{
public SetupDialog()
: base(nameof(SetupDialog))
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
}
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
public override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
{
return innerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text("Wie viel rekursionen sollen erstellt werden?")
});
}
}
So, I got the ResumeDialog method to be triggerd.
To trigger the ResumeDialog method, the dialog that ended and the dialog you want to resume to has to be on the same dialog stack!
My scenario were BotDialogContext[RootDialog] -> RootDialogContext[SetupDialog], but I need a context like this BotDialogContext[RootDialog, SetupDialog].
One of the problems is, that every ComponentDialog you start creates its own DialogContext. So if you begin a dialog within a dialog its pushed on the stack of the inner DialogContext and so on. But the description of the ResumeDialog method is
Called when a child dialog on the parent's dialog stack completed this turn, returning control to this dialog component.
To put an child dialog on the parents dialog stack you had to call the BeginDialog method on the outer dialog context. This context also needs to have the "child dialog" in its dialogset.
Here my example:
RootDialog.cs:
public class RootDialog : ComponentDialog
{
private int recursions;
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
public async Task<DialogTurnResult> ProcessStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Process step in root dialog")
});
if(Dialogs.Find(nameof(RecursiveDialog)) == null)
{
AddDialog(new RecursiveDialog(new DialogSet(), recursions));
}
if (recursions > 0)
{
return await stepContext.BeginDialogAsync(nameof(RecursiveDialog));
}
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Recursion lower or eqaul 0")
});
}
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
if (true)
{
return await outerDc.BeginDialogAsync(nameof(SetupDialog));
}
var dialogContext = CreateChildContext(outerDc);
await dialogContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Begin root dialog")
});
return await base.BeginDialogAsync(outerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
if(innerDc.ActiveDialog != null)
{
return await innerDc.ContinueDialogAsync();
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
{
recursions = Convert.ToInt32(result);
await outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text($"Resume root dialog with recursion value {recursions}")
});
return await outerDc.BeginDialogAsync(nameof(WaterfallDialog));
}
}
SetupDialog.cs:
public class SetupDialog : ComponentDialog
{
public SetupDialog()
: base(nameof(SetupDialog))
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
}
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
public override Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
return base.BeginDialogAsync(outerDc, options, cancellationToken);
}
protected override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
{
return innerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text("Wie viel rekursionen sollen erstellt werden?")
});
}
}
DialogBot.cs:
public class DialogBot<T> : ActivityHandler
where T : Dialog
{
protected readonly DialogSet Dialogs;
protected readonly BotState ConversationState;
protected readonly BotState UserState;
protected readonly ILogger Logger;
public DialogBot(ConversationState conversationState, UserState userState, IEnumerable<Dialog> dialogs, ILogger<DialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Logger = logger;
Dialogs = new DialogSet(conversationState.CreateProperty<DialogState>(nameof(DialogState)));
foreach(var dialog in dialogs)
{
Dialogs.Add(dialog);
}
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occured during the turn.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false);
if (dc.ActiveDialog != null)
{
await dc.ContinueDialogAsync();
}
else
{
// Run the Dialog with the new message Activity.
await dc.BeginDialogAsync(typeof(T).Name, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
}
}
Inside the IEnumerable are both (RootDialog & SetupDialog) to get both dialogs into to the BotDialogContext and DialogSet

ContinueConversationAsync() requires users to chat first after publishing bot

After publishing the bot, the user needs to chat with the bot again first in order send a proactive message. I followed this sample but instead of storing the conversation reference in the variable, I stored it in cosmosDB. However I still can't send proactive message if the user has not chatted with the bot after publishing. Is there a way to send proactive message even when the user has not chatted with the bot after publishing?
DialogBot
private async void AddConversationReference(ITurnContext turnContext)
{
var userstate = await _userProfileAccessor.GetAsync(turnContext, () => new BasicUserState());
userstate.SavedConversationReference = turnContext.Activity.GetConversationReference();
_conversationReferences.AddOrUpdate(userstate.SavedConversationReference.User.Id, userstate.SavedConversationReference, (key, newValue) => userstate.SavedConversationReference);
}
protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
AddConversationReference(turnContext);
return base.OnConversationUpdateActivityAsync(turnContext, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
AddConversationReference(turnContext);
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
api/notify
namespace SabikoBotV2.Controllers
{
[Route("api/notify")]
public class NotifyController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter _adapter;
private readonly string _appId;
private readonly ConcurrentDictionary<string, ConversationReference> _conversationReferences;
private readonly IStatePropertyAccessor<BasicUserState> _userProfileAccessor;
public NotifyController(UserState userState, IBotFrameworkHttpAdapter adapter, ICredentialProvider credentials, ConcurrentDictionary<string, ConversationReference> conversationReferences)
{
_userProfileAccessor = userState.CreateProperty<BasicUserState>("UserProfile");
_adapter = adapter;
_conversationReferences = conversationReferences;
_appId = ((SimpleCredentialProvider)credentials).AppId;
if (string.IsNullOrEmpty(_appId))
{
_appId = Guid.NewGuid().ToString(); //if no AppId, use a random Guid
}
}
public async Task<IActionResult> Get()
{
try
{
foreach (var conversationReference in _conversationReferences.Values)
{
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, BotCallback, default(CancellationToken));
}
return new ContentResult()
{
Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
};
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
var userstate = await _userProfileAccessor.GetAsync(turnContext, () => new BasicUserState(), cancellationToken);
if (userstate.SavedConversationReference.ServiceUrl != null && userstate.SavedConversationReference.ServiceUrl != string.Empty)
{
MicrosoftAppCredentials.TrustServiceUrl(userstate.SavedConversationReference.ServiceUrl);
}
else if (turnContext.Activity.ServiceUrl != null && turnContext.Activity.ServiceUrl != string.Empty)
{
MicrosoftAppCredentials.TrustServiceUrl(turnContext.Activity.ServiceUrl);
}
else
{
MicrosoftAppCredentials.TrustServiceUrl("https://facebook.botframework.com/");
}
if(userstate.Reminders != null)
{
foreach (var reminder in userstate.Reminders)
{
if (reminder.DateAndTime.TrimMilliseconds() == DateTimeNowInGmt().TrimMilliseconds())
{
var timeProperty = new TimexProperty(reminder.DateAndTimeTimex);
var naturalDate = timeProperty.ToNaturalLanguage(DateTimeNowInGmt().TrimMilliseconds());
await turnContext.SendActivityAsync($"It's {DateTimeNowInGmt().ToLongDateString()}. \n\nReminding you to {reminder.Subject}.");
}
}
}
}
public static DateTime DateTimeNowInGmt()
{
TimeZoneInfo phTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time");
var t = DateTime.Now;
DateTime phTime = TimeZoneInfo.ConvertTime(t, phTimeZone);
return phTime;
}
}
}

Interrupt to run a new dialog on Botframework

I am trying to migrate my codes from the old Bot Framework V4(last January) to the new Bot framework v4 design. What I like is to call a dialog when the user input. "Get started". I can do this easily on the previous design but now I can't. Also this is just text now. what if i want to use a LUIS intent? Thank you.
protected override async Task<DialogTurnResult> OnBeginDialogAsync(
DialogContext innerDc,
object options,
CancellationToken cancellationToken = default(CancellationToken))
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(
DialogContext innerDc,
CancellationToken cancellationToken)
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
private async Task<DialogTurnResult> InterruptAsync(
DialogContext innerDc,
CancellationToken cancellationToken)
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message)
{
var text = innerDc.Context.Activity.Text.ToLowerInvariant();
switch (text)
{
case "help":
case "?":
await innerDc.Context.SendActivityAsync($"Show Help...",
cancellationToken: cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
case "get started":
return await innerDc.BeginDialogAsync(nameof(DialogA));
case "cancel":
case "quit":
await innerDc.Context.SendActivityAsync($"Cancelling",
cancellationToken: cancellationToken);
return await innerDc.CancelAllDialogsAsync();
}
}
return null;
}
DialogA
namespace practiceNewBot.Dialogs
{
public class DialogA : CancelAndHelpDialog
{
public DialogA() : base(nameof(DialogA))
{
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
FirstStepAsync,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"start of dialog a"), cancellationToken);
return await stepContext.PromptAsync(
nameof(ChoicePrompt),
new PromptOptions
{
Prompt = MessageFactory.Text($"choose a dialog"),
Choices = new List<Choice>
{
new Choice
{
Value = "Dialog A Child",
Synonyms = new List<string>
{
"dialog a child",
},
},
new Choice
{
Value = "Dialog B Child",
Synonyms = new List<string>
{
"dialog b chilc",
},
},
new Choice
{
Value = "Cancel",
Synonyms = new List<string>
{
"cancel",
},
},
},
RetryPrompt = MessageFactory.Text("Please choose one of the options."),
},
cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var response = (stepContext.Result as FoundChoice).Value.ToLower();
switch (response)
{
case "dialog a child":
return await stepContext.BeginDialogAsync(nameof(DialogA), cancellationToken: cancellationToken);
case "dialog b child":
return await stepContext.BeginDialogAsync(nameof(DialogB), cancellationToken);
case "cancel":
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Ok, Cancelled."));
return await stepContext.CancelAllDialogsAsync(cancellationToken: cancellationToken);
default:
break;
}
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"end of dialog a"), cancellationToken);
return await stepContext.EndDialogAsync();
}
}
}

Categories

Resources