I'm creating a bot using botframework , c# . I'm using also qnamaker. My questions is how to store in database the questions and answer of users. For the moment i can store in log table only message send by users as below :
MessagesController.cs :
// if (activity.Type == ActivityTypes.Message)
{
*************************
// Log to Database
// *************************
// Instantiate the BotData dbContext
Model.qnamakerbotdataEntities DB = new Model.qnamakerbotdataEntities();
// Create a new UserLog object
Model.UserLog NewUserLog = new Model.UserLog();
// Set the properties on the UserLog object
NewUserLog.Channel = activity.ChannelId;
NewUserLog.UserID = activity.From.Id;
NewUserLog.UserName = activity.From.Name;
NewUserLog.created = DateTime.UtcNow;
NewUserLog.Message = activity.Text.Truncate(500);
// Add the UserLog object to UserLogs
DB.UserLogs.Add(NewUserLog);
// Save the changes to the database
DB.SaveChanges();
Do you have an idea how to store message send by users and response of the bot ?
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using QnABot.API;
using Microsoft.Bot.Builder.Dialogs.Internals;
namespace QnABot.Dialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context,
IAwaitable<object> result)
{
//var activity = await result as Activity;
//// Prompt text
//await context.PostAsync("Feel free to ask me");
var privateData = context.PrivateConversationData;
var privateConversationInfo = IncrementInfoCount(privateData,
BotStoreType.BotPrivateConversationData.ToString());
var conversationData = context.ConversationData;
var conversationInfo = IncrementInfoCount(conversationData,
BotStoreType.BotConversationData.ToString());
var userData = context.UserData;
var userInfo = IncrementInfoCount(userData,
BotStoreType.BotUserData.ToString());
context.Wait(QnADialog);
PrivateData.SetValue(BotStoreType.BotPrivateConversationData.ToString(),
privateConversationInfo);
conversationData.SetValue(BotStoreType.BotConversationData.ToString(), conversationInfo);
userData.SetValue(BotStoreType.BotUserData.ToString(), userInfo);
}
private async Task QnADialog(IDialogContext context, IAwaitable<object> result)
{
var activityResult = await result as Activity;
var query = activityResult.Text;
var qnaResult = QnaApi.GetFirstQnaAnswer(query);
if (qnaResult == null)
{
string message = $"Sorry, I did not understand . Please
reformulate your question";
}
else
{
await context.PostAsync(qnaResult.answers[0].answer);
}
context.Wait(MessageReceivedAsync);
}
public class BotDataInfo
{
public int Count { get; set; }
}
private BotDataInfo IncrementInfoCount(IBotDataBag botdata, string key)
{
BotDataInfo info = null;
if (botdata.ContainsKey(key))
{
info = botdata.GetValue<BotDataInfo>(key);
info.Count++;
}
else
info = new BotDataInfo() { Count = 1 };
return info;
}
}
}
You could so something like below. Please note I did not have all your code to make this compile, but you should be able to adjust it if needed. the main part is creating a reply like this var reply = activityResult.CreateReply(); and then setting the text in the message variable in your if-else then setting the text of the reply then sending it
NewUserLog.Message = reply.Text;
await context.PostAsync(reply);
private async Task QnADialog(IDialogContext context, IAwaitable<object> result)
{
var activityResult = await result as Activity;
var query = activityResult.Text;
var reply = activityResult.CreateReply();
var qnaResult = QnaApi.GetFirstQnaAnswer(query);
string message = "";
if (qnaResult == null)
{
message = $"Sorry, I did not understand. Please reformulate your question";
}
else
{
message = qnaResult.answers[0].answer;
}
reply.Text = message;
Model.qnamakerbotdataEntities DB = new Model.qnamakerbotdataEntities();
// Create a new UserLog object
Model.UserLog NewUserLog = new Model.UserLog();
// Set the properties on the UserLog object
NewUserLog.Channel = reply.ChannelId;
NewUserLog.UserID = reply.From.Id;
NewUserLog.UserName = reply.From.Name;
NewUserLog.created = DateTime.UtcNow;
NewUserLog.Message = reply.Text;
await context.PostAsync(reply);
context.Wait(MessageReceivedAsync);
}
Related
There is a JSON-RPC API, which I'm currently implementing. It can be tested here.
The problem is that if an incorrect DTO model is passed to SendAsync<TResponse>, JsonSerializer.Deserialize is going to throw a JsonException, which is not handled by my code. I know I've got to use SetException in some way, but I don't know how to do it, so here is the question. The exception message should be printed in the console as well.
public sealed class Client : IDisposable
{
...
private readonly ConcurrentDictionary<long, IResponseHandler> _handlers = new();
...
public Task StartAsync(CancellationToken cancellationToken)
{
_ = Task.Run(async () =>
{
await foreach (var message in _client.Start(cancellationToken))
{
using var response = JsonDocument.Parse(message);
try
{
var requestId = response.RootElement.GetProperty("id").GetInt32();
// TODO: Handle JsonException errors via `SetException`?
// TODO: Show error when incorrect input parameters are filled
if (_handlers.ContainsKey(requestId))
{
_handlers[requestId].SetResult(message);
_handlers.TryRemove(requestId, out _);
}
}
catch (KeyNotFoundException)
{
// My point is that a message should be processed only if it doesn't include `id`,
// because that means that the message is an actual web socket subscription.
_messageReceivedSubject.OnNext(message);
}
}
}, cancellationToken);
...
return Task.CompletedTask;
}
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params
};
//var tcs = new TaskCompletionSource<TResponse>();
//_requestManager.Add(request.Id, request, tcs);
var handler = new ResponseHandlerBase<TResponse>();
_handlers[request.Id] = handler;
var message = JsonSerializer.Serialize(request);
_ = _client.SendAsync(message);
return handler.Task;
//return tcs.Task;
}
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params).ConfigureAwait(false);
return response.Result;
}
...
private interface IResponseHandler
{
void SetResult(string payload);
}
private class ResponseHandlerBase<TRes> : IResponseHandler
{
private readonly TaskCompletionSource<TRes> _tcs = new();
public Task<TRes> Task => _tcs.Task;
public void SetResult(string payload)
{
var result = JsonSerializer.Deserialize(payload, typeof(TRes));
_tcs.SetResult((TRes) result);
}
}
}
Coincidentally, I did something very similar while live-coding a TCP/IP chat application last week.
Since in this case you already have an IAsyncEnumerable<string> - and since you can get messages other than responses - I recommend also exposing that IAsyncEnumerable<string>:
public sealed class Client : IDisposable
{
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
// TODO: parse and handle responses for our requests
yield return message;
}
}
}
You can change this to be Rx-based if you want (_messageReceivedSubject.OnNext), but I figure if you already have IAsyncEnumerable<T>, then you may as well keep the same abstraction.
Then, you can parse and detect responses, passing along all other messages:
public sealed class Client : IDisposable
{
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null)
{
// TODO: handle
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
}
Then, you can define your collection of outstanding requests and handle messages that are for those requests:
public sealed class Client : IDisposable
{
private readonly ConcurrentDictionary<int, TaskCompletionSource<JsonDocument>> _requests = new();
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null && _requests.TryRemove(requestId.Value, out var tcs))
{
tcs.TrySetResult(response);
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
}
Note the usage of ConcurrentDictionary.TryRemove, which is safer than accessing the value and then removing it.
Now you can write your general SendAsync. As I note in my video, I prefer to split up the code that runs synchronously in SendAsync and the code that awaits the response:
public sealed class Client : IDisposable
{
...
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params,
};
var tcs = new TaskCompletionSource<JsonDocument>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async Task<TResponse> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message);
var response = await tcs.Task;
return JsonSerializer.Deserialize(response, typeof(TResponse));
}
}
}
I've removed the "handler" concept completely, since it was just providing the type for JsonSerializer.Deserialize. Also, by using a local async method, I can use the async state machine to propagate exceptions naturally.
Then, your higher-level methods can be built on this:
public sealed class Client : IDisposable
{
...
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params);
return response.Result;
}
}
So the final code ends up being:
public sealed class Client : IDisposable
{
private readonly ConcurrentDictionary<int, TaskCompletionSource<JsonDocument>> _requests = new();
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null && _requests.TryRemove(requestId.Value, out var tcs))
{
tcs.TrySetResult(response);
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params,
};
var tcs = new TaskCompletionSource<JsonDocument>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async Task<TResponse> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message);
var response = await tcs.Task;
return JsonSerializer.Deserialize(response, typeof(TResponse));
}
}
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params);
return response.Result;
}
}
You may also want to check out David Fowler's Project Bedrock, which may simplify this code quite a bit.
I'm using bot framework V3 with C#.
I need to identify when my bot is idle for more than 5 minutes.
I've tried to handle bot idleness via the MessageController but my attempt seems not to work out.
switch (activity.Type)
{
case ActivityTypes.Message:
await Task.Delay(5000).ContinueWith(async (t) =>
{
var reply = activity.CreateReply();
var myMessage = "Bot time out. Bye";
reply.Text = myMessage;
await connector.Conversations.ReplyToActivityAsync(reply);
});
await Task.Factory.StartNew(() => Conversation.SendAsync(activity, () => new Dialogs.RootDialog(luisService).DefaultIfException()));
}
break;
}
What could be wrong?
Any sample could you could share please?
Thx in advance!
First, you're only delaying for 5 seconds (5000 miliseconds) and not 5 minutes.
Anyhow, you can try the following approach. Add this class:
public static class TimeoutConversations
{
const int TimeoutLength = 10;
private static Timer _timer;
private static TimeSpan _timeoutLength;
static TimeoutConversations()
{
_timeoutLength = TimeSpan.FromSeconds(TimeoutLength);
_timer = new Timer(CheckConversations, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
}
static ConcurrentDictionary<string, UserInfo> Conversations = new ConcurrentDictionary<string, UserInfo>();
static async void CheckConversations(object state)
{
foreach (var userInfo in Conversations.Values)
{
if (DateTime.UtcNow - userInfo.LastMessageReceived >= _timeoutLength)
{
UserInfo removeUserInfo = null;
Conversations.TryRemove(userInfo.ConversationReference.User.Id, out removeUserInfo);
var activity = userInfo.ConversationReference.GetPostToBotMessage();
//clear the dialog stack and conversation state for this user
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
//botData.UserData.Clear();
botData.ConversationData.Clear();
botData.PrivateConversationData.Clear();
await botData.FlushAsync(CancellationToken.None);
}
MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
var connectorClient = new ConnectorClient(new Uri(activity.ServiceUrl), ConfigurationManager.AppSettings["MicrosoftAppId"], ConfigurationManager.AppSettings["MicrosoftAppPassword"]);
var reply = activity.CreateReply("I haven't heard from you in awhile. Let me know when you want to talk.");
connectorClient.Conversations.SendToConversation(reply);
//await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
}
}
public static void MessageReceived(Activity activity)
{
UserInfo userInfo = null;
if (Conversations.TryGetValue(activity.From.Id, out userInfo))
{
userInfo.LastMessageReceived = DateTime.UtcNow;
}
else
{
Conversations.TryAdd(activity.From.Id, new UserInfo()
{
ConversationReference = activity.ToConversationReference(),
LastMessageReceived = DateTime.UtcNow
});
}
}
}
public class UserInfo
{
public ConversationReference ConversationReference { get; set; }
public DateTime LastMessageReceived { get; set; }
}
And then in messages controller, call:
TimeoutConversations.MessageReceived(activity);
In this example, it is doing a 10 second timeout, checking every 5 seconds. This is a basic (somewhat sloppy) timer to time out conversations. You'll probably run into bugs, but you can tweak it until it fits your needs. Using an azure queue or something might be better.
Here is a DCR for v4 to implement this basic functionality:
https://github.com/microsoft/botframework-sdk/issues/5237
I am using Bot Framework V4, AD Authentication for our bot is working fine.But when ever i am trying to user new session it is taking the same Token by which it is logged previously. So I am getting the same data in all the sessions. I am using AuthenticationDialog provided by Enterprise Bot Template
Actual: I am getting logged in once and it is staying logged in all sessions(even in other machines)
Expected: I expect every session should take me to the sign in card(OAurth card)
public class AuthenticationDialog : ComponentDialog
{
private static AuthenticationResponses _responder = new AuthenticationResponses();
public AuthenticationDialog(string connectionName)
: base(nameof(AuthenticationDialog))
{
InitialDialogId = nameof(AuthenticationDialog);
ConnectionName = connectionName;
var authenticate = new WaterfallStep[]
{
PromptToLogin,
FinishLoginDialog,
};
AddDialog(new WaterfallDialog(InitialDialogId, authenticate));
AddDialog(new OAuthPrompt(DialogIds.LoginPrompt, new OAuthPromptSettings()
{
ConnectionName = ConnectionName,
Title = AuthenticationStrings.TITLE,
Text = AuthenticationStrings.PROMPT,
}));
}
private string ConnectionName { get; set; }
private async Task<DialogTurnResult> PromptToLogin(WaterfallStepContext sc, CancellationToken cancellationToken)
{
return await sc.PromptAsync(AuthenticationResponses.ResponseIds.LoginPrompt, new PromptOptions());
}
private async Task<DialogTurnResult> FinishLoginDialog(WaterfallStepContext sc, CancellationToken cancellationToken)
{
var activity = sc.Context.Activity;
if (sc.Result != null)
{
var tokenResponse = sc.Result as TokenResponse;
if (tokenResponse?.Token != null)
{
var user = await GetProfile(sc.Context, tokenResponse);
await _responder.ReplyWith(sc.Context, AuthenticationResponses.ResponseIds.SucceededMessage, new { name = user.DisplayName });
return await sc.EndDialogAsync(tokenResponse);
}
}
else
{
await _responder.ReplyWith(sc.Context, AuthenticationResponses.ResponseIds.FailedMessage);
}
return await sc.EndDialogAsync();
}
private async Task<User> GetProfile(ITurnContext context, TokenResponse tokenResponse)
{
var token = tokenResponse;
var client = new GraphClient(token.Token);
return await client.GetMe();
}
private class DialogIds
{
public const string LoginPrompt = "loginPrompt";
}
}
This is a known issue in WebChat. When you use the same user id for every conversation, the conversation will reference the same data stores. To resolve this issue, I would recommend generating random user ids for each conversation.
Hope this helps.
I'm creating a bot using Bot Framework, in web channel and direct line.
i Have a luis dialog which call some forms ( flow form ) .
I have to ask for each form the contract number and some other data . How to store just one time contract number and to go ahead to other field and that to response the right answer for that contract number of the user.
i was trying to ask in the begging the contract number and name of the person and to check if they match..if yes to let the user to use the bot...but i cannot find the right logic code.
Can you help me please with any idea how to ask just one time the contract number and to use in all my dialog and forms ?
Below is my code :
MessagesController.cs :
public class MessagesController : ApiController
{
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isTypingReply = activity.CreateReply();
isTypingReply.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isTypingReply);
if (activity.Type == ActivityTypes.Message)
{
activity.Locale = "en-US";
Helpers.SaveActivityDataToDB(activity);
await Conversation.SendAsync(activity, () => new LuisDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Luis.cs
[LuisIntent("Greeting")]
public async Task Greeting(IDialogContext context, LuisResult result)
{
await context.SayAsync(text: "Welcome");
}
[LuisIntent("balance")]
public async Task balance(IDialogContext context, LuisResult result)
{
var balanca = new FormDialog<BalanceForm>(
new BalanceForm(),
BalanceForm.BuildForm,
FormOptions.PromptInStart,
result.Entities);
context.Call<BalanceForm>(balanca, BalanceCompleted);
}
private async Task BalanceCompleted(IDialogContext context, IAwaitable<BalanceForm> result)
{
BalanceForm form = null;
try
{
form = await result;
}
catch (OperationCanceledException)
{
}
if (form == null)
{
await context.PostAsync("Try again please!");
}
else
{
//call the LossForm service to complete the form fill
var message = $"Thnx";
await context.PostAsync(message);
}
context.Wait(this.MessageReceived);
}
BalanceForm.cs
[Serializable]
public class BalanceForm
{
[Prompt("What is your contract number?")]
public string contract;
public static IForm<BalanceForm> BuildForm()
{
OnCompletionAsyncDelegate<BalanceForm> wrapUpRequest = async
(context, state) =>
{
using (BotModelDataContext BotDb = new BotModelDataContext())
{
//search in database
string wrapUpMessage = "Dear " + house.Firstname + "," + "your balance is " + house.Balance;
await context.PostAsync(wrapUpMessage);
};
return new FormBuilder<BalanceForm>()
.Message("We have to ask you some information")
.Field(nameof(contract))
.OnCompletion(wrapUpRequest)
//.Confirm("Are you sure: Yes or No ")
.Build();
}
}
}
Can you help me please with any idea how to ask just one time the contract number and to use in all my dialog and forms ?
Based on your requirement, I create a sample to prompt user for contract number and save the value in UserData so that I can get the contract number that user provided from UserData in child dialog(s). The following sample code is for your reference.
In RootDialog:
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
// calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
var contractnumber = "";
if (!context.UserData.TryGetValue<string>("contract_number", out contractnumber))
{
//prompt for contract number
PromptDialog.Text(
context: context,
resume: AfterGetContractNumber,
prompt: "Please provide your contract number.",
retry: "Please try again."
);
}
else
{
await context.Forward(new BaiscLuisDialog(), AfterLuisDialog, context.Activity, CancellationToken.None);
}
}
private async Task AfterGetContractNumber(IDialogContext context, IAwaitable<string> result)
{
string contractnumber = await result;
context.UserData.SetValue<string>("contract_number", contractnumber);
await context.PostAsync($"OK, received your contract number: {contractnumber}.");
context.Done(this);
}
private async Task AfterLuisDialog(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceivedAsync);
}
}
In BaiscLuisDialog:
[LuisIntent("balance")]
public async Task BalanceIntent(IDialogContext context, LuisResult result)
{
//call BalanceForm
//and pass contract number that user provided to BalanceForm contract_number
var balanca = new Microsoft.Bot.Builder.FormFlow.FormDialog<BalanceForm>(new BalanceForm() { contract_number = context.UserData.GetValue<string>("contract_number") },
BalanceForm.BuildForm,
Microsoft.Bot.Builder.FormFlow.FormOptions.PromptInStart,
result.Entities);
context.Call<BalanceForm>(balanca, BalanceCompleted);
}
private async Task BalanceCompleted(IDialogContext context, IAwaitable<BalanceForm> result)
{
context.Wait(MessageReceived);
}
In BalanceForm:
[Serializable]
public class BalanceForm
{
public string contract_number;
[Prompt("What is your contract number?")]
public string contract;
public string your_other_field;
public static IForm<BalanceForm> BuildForm()
{
OnCompletionAsyncDelegate<BalanceForm> wrapUpRequest = async
(context, state) =>
{
//using (BotModelDataContext BotDb = new BotModelDataContext())
//{
// //search in database
// string wrapUpMessage = "Dear " + house.Firstname + "," + "your balance is " + house.Balance;
// await context.PostAsync(wrapUpMessage);
//};
//your code logic here
var contractnumber = "";
context.UserData.TryGetValue<string>("contract_number", out contractnumber);
string wrapUpMessage = " Form completed! Your contract number is " + contractnumber;
var replymes = context.MakeMessage();
replymes.Text = wrapUpMessage;
await context.PostAsync(replymes);
};
return new FormBuilder<BalanceForm>()
.Message("We have to ask you some information")
.Field(new FieldReflector<BalanceForm>(nameof(contract)).SetActive(state => state.contract_number == null))
.Field(nameof(your_other_field))
.OnCompletion(wrapUpRequest)
//.Confirm("Are you sure: Yes or No ")
.Build();
}
}
Test result:
I've updated my NuGet packages to use the version 1.2.0.1 of the Microsoft Bot Framework.
Some breaking changes were reported here, and I managed to fix the build errors. But the application is not working anymore..
I have two problems:
The code throws an InvalidIntentHandlerException when I send a message an utterance to the controller.
In my 'intent' method (decorated with the LuisIntent attribute) it was possible to read the value of the entities. Like so:
[Serializable]
[LuisModel("xxxxx", "xxxx")]
public class BookFlightDialog : LuisDialog<BookFlightForm>
{
private readonly BuildFormDelegate<BookFlightForm> BuildForm;
internal BookFlightDialog(BuildFormDelegate<BookFlightForm> buildForm)
{
BuildForm = buildForm;
}
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
await context.PostAsync("I'm sorry. I didn't understand you.");
context.Wait(MessageReceived);
}
[LuisIntent("BookAFlight")]
public async Task BookAFlight(IDialogContext context, LuisResult result)
{
var form = new BookFlightForm();
// var entities = new List<EntityRecommendation>(result.Entities);
var locations = result.Entities.Where(e => e.Type.Equals("builtin.geography") || e.Type.Equals("builtin.geography.city")).OrderBy(e => e.StartIndex);
if (locations.Any())
{
form.LocationFrom = locations.First().Name;
if (locations.Count() == 2)
{
form.LocationTo = locations.Skip(1).First().Name;
}
}
var date = result.Entities.FirstOrDefault(e => e.Type == "builtin.datetime.date");
if (date != null) form.DepartureDate = DateTime.Parse(date.Name);
var formDialog = new FormDialog<BookFlightForm>(form, BuildForm, FormOptions.PromptInStart);
context.Call(formDialog, OnComplete);
}
private async Task OnComplete(IDialogContext context, IAwaitable<BookFlightForm> result)
{
BookFlightForm booking;
try
{
booking = await result;
}
catch (OperationCanceledException)
{
await context.PostAsync("Ok, see you later.");
return;
}
if (booking != null)
{
var service = new SkyScannerService();
var possibilities = await service.Search(booking);
await context.PostAsync(possibilities);
}
else
{
await context.PostAsync("Form returned empty response!");
}
context.Wait(MessageReceived);
}
}
How do I fix the exception and how do I read the value of the entities?
Thanks once again!
This is because you are not using inbuilt LuisResult class by having using LuisResult = Bots.Results.LuisResult;. Replace it with using Microsoft.Bot.Builder.Luis.Models;.