Reputation:
Setup
I have a a bot that runs on .NET + Bot Framework + Azure + Facebook Messenger.
Initial Problem
I was trying to solve a problem when sending several messages to the bot triggers an exception and HTTP error 412. Microsoft describes this problem here: https://learn.microsoft.com/en-us/bot-framework/troubleshoot-general-problems#what-causes-an-error-with-http-status-code-412-precondition-failed-or-http-status-code-409-conflict
First Solution
In the page above, Microsoft provides an outdated sample code to resolve this issue. In this github issue, there is a revised version of that code that is supposed to work. I put it inside the constructor of my MessageController:
static MessagesController()
{
// Prevent exception in the bot and HTTP error 412 when the user
// sends multiple messages in quick succession. This may cause
// potential problems with consistency of getting/setting user
// properties.
// See https://learn.microsoft.com/en-us/bot-framework/troubleshoot-general-problems#what-causes-an-error-with-http-status-code-412-precondition-failed-or-http-status-code-409-conflict
// for details. The above link contains wrong code sample, revised
// code is from here: https://github.com/Microsoft/BotBuilder/issues/2345
var builder = new ContainerBuilder();
builder
.Register(c => new CachingBotDataStore(c.ResolveKeyed<IBotDataStore<BotData>>(typeof(ConnectorStore)), CachingBotDataStoreConsistencyPolicy.LastWriteWins))
.As<IBotDataStore<BotData>>()
.AsSelf()
.InstancePerLifetimeScope();
builder.Update(Conversation.Container);
}
Second Problem
Now, the exception still occurs when I send several messages to the bot in a quick succession. However, it changed from HTTP error 412 to something else:
One or more errors occurred. at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task1.GetResultCore(Boolean waitCompletionNotification) at System.Threading.Tasks.Task1.get_Result() at MyBot.SetUserDataProperty(Activity activity, String PropertyName, String ValueToSet) in C:\Users\xxx.cs:line 230
Update: I've checked the InnerException
of the above and it turns out to be the same old HTTP error 412:
The remote server returned an error: (412) Precondition Failed.
The offending code is a function that writes to the bot storage. The line 230 referenced above is the last line of this function:
public static void SetUserDataProperty(Activity activity, string PropertyName, string ValueToSet)
{
StateClient client = activity.GetStateClient();
BotData userData = client.BotState.GetUserData(activity.ChannelId, activity.From.Id);
userData.SetProperty<string>(PropertyName, ValueToSet);
//client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
// Await async call without making the function asynchronous:
var temp = Task.Run(() => client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData)).Result;
}
Question
What else can I do to make sure that the user is able to send multiple messages in quick succession without triggering an exception when writing to the BotState storage?
Upvotes: 2
Views: 802
Reputation: 3426
I think there are a few issues here
The way you are trying to do this activity.GetStateClient();
is a only intended to be used for prototyping. We do no reccomend this method for production level code. You can set user data like context.UserData.SetValue("food", "Nachos" );
in the dialog and the values will automagically get saved when the dialog is serialized.
Most likely you are calling this method SetUserDataProperty
from a dialog so when you do this var temp = Task.Run(() => client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData)).Result;
it is conflicting and causing the error.
please review this blog post to learn more
Here is how to implement your follow up question:
if (activity.Type == ActivityTypes.Message)
{
var message = activity as IMessageActivity;
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
var key = new AddressKey()
{
BotId = message.Recipient.Id,
ChannelId = message.ChannelId,
UserId = message.From.Id,
ConversationId = message.Conversation.Id,
ServiceUrl = message.ServiceUrl
};
ConversationReference r = new ConversationReference();
var userData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None);
userData.SetProperty("key 1", "value1");
userData.SetProperty("key 2", "value2");
await botDataStore.SaveAsync(key, BotStoreType.BotUserData, userData, CancellationToken.None);
await botDataStore.FlushAsync(key, CancellationToken.None);
}
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
you will need to implement this class or something similar:
public class AddressKey : IAddress
{
public string BotId { get; set; }
public string ChannelId { get; set; }
public string ConversationId { get; set; }
public string ServiceUrl { get; set; }
public string UserId { get; set; }
}
Upvotes: 3