'Botframework v4 same state for all users C#
I'm trying to migrate my botframework APP v3 to v4, I followed the instruction from microsoft documentation and everything works fine except state control, in my bot the state is shared for all user and for all channels (directline, telegram).
I saw this topic UserProfile state persistent between users in bot v4 but even forcing web random ID the state still share between users.
My bot has been created with Core bot template provided by Microsoft. You can see my C# code below. I'll skip to post Maindialog due to only business rules there in a waterfall dialog, but if needed I can post too.
Thanks for the help.
Startup.cs
public class Startup
{
private const string BotOpenIdMetadataKey = "BotOpenIdMetadata";
private static readonly AzureBlobStorage _UserStorage = new AzureBlobStorage("DefaultEndpointsProtocol=https;AccountName=evabotwebbe9c;AccountKey=wsU18jImJVSX+2vq6l0flx9Ou83hcDyrxie0tUN7fjxMV3bfHhYJuFobmq0h/TXU/pBBOvfpGVUlHtuqn7cNVw==;EndpointSuffix=core.windows.net", "botuserstate");
private static readonly AzureBlobStorage _ConversationStorage = new AzureBlobStorage("DefaultEndpointsProtocol=https;AccountName=evabotwebbe9c;AccountKey=wsU18jImJVSX+2vq6l0flx9Ou83hcDyrxie0tUN7fjxMV3bfHhYJuFobmq0h/TXU/pBBOvfpGVUlHtuqn7cNVw==;EndpointSuffix=core.windows.net", "botconversationstate");
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
if (!string.IsNullOrEmpty(Configuration[BotOpenIdMetadataKey]))
ChannelValidation.OpenIdMetadataUrl = Configuration[BotOpenIdMetadataKey];
// Create the credential provider to be used with the Bot Framework Adapter.
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
// Create the Bot Framework Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, WebSocketEnabledHttpAdapter>();
// Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
services.AddSingleton<IStorage, AzureBlobStorage>();
// Create the User state. (Used in this bot's Dialog implementation.)
var userState = new UserState(_UserStorage);
// Create the Conversation state. (Used by the Dialog system itself.)
var conversationState = new ConversationState(_ConversationStorage);
// Add the states as singletons
services.AddSingleton(userState);
services.AddSingleton(conversationState);
services.AddSingleton(sp =>
{
userState = sp.GetService<UserState>();
conversationState = sp.GetService<ConversationState>();
// Create the Conversation state. (Used by the Dialog system itself.)
return new BotStateSet(userState, conversationState);
});
// The Dialog that will be run by the bot.
services.AddSingleton<MainDialog>();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, DialogAndWelcomeBot<MainDialog>>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseWebSockets();
//app.UseHttpsRedirection();
app.UseMvc();
}
}
DialogAndWelcomeBot.cs
public class DialogAndWelcomeBot<T> : DialogBot<T> where T : Dialog
{
protected IConfiguration Configuration;
private BotState _conversationState;
private BotState _userState;
protected IStatePropertyAccessor<DialogState> accessor;
protected Dialogo dialogo;
protected Dialog Dialog;
public DialogAndWelcomeBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger, IConfiguration configuration)
: base(conversationState, userState, dialog, logger)
{
Configuration = configuration;
dialogo = new Dialogo();
_conversationState = conversationState;
_userState = userState;
Dialog = dialog;
}
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)
{
dialogo = await VerificarDialogo();
await IniciarDialogo(turnContext, cancellationToken);
}
}
}
private async Task IniciarDialogo(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
accessor = _conversationState.CreateProperty<DialogState>("DialogState");
var dialog = Dialog;
var dialogSet = new DialogSet(accessor);
dialogSet.Add(dialog);
var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
var results = await dialogContext.ContinueDialogAsync(cancellationToken);
if (results.Status == DialogTurnStatus.Empty)
{
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());
await dialogContext.BeginDialogAsync(dialog.Id, dialogo, cancellationToken);
}
}
DialogBot.cs
public class DialogBot<T> : ActivityHandler where T : Dialog
{
private readonly Dialog Dialog;
private BotState _conversationState;
private BotState _userState;
protected readonly ILogger Logger;
public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
_conversationState = conversationState;
_userState = userState;
Dialog = dialog;
Logger = logger;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Client notifying this bot took to long to respond (timed out)
if (turnContext.Activity.Code == EndOfConversationCodes.BotTimedOut)
{
await turnContext.SendActivityAsync(MessageFactory.Text("Desculpe-me, mas parece que a conexão com a internet está ruim e isto irá afetar o desempenho da nossa conversa."), cancellationToken);
return;
}
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Message Activity log");
// Run the Dialog with the new message Activity.
await Dialog.Run(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
DialogExtensions.cs
public static class DialogExtensions
{
public static async Task Run(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor<DialogState> accessor, CancellationToken cancellationToken = default(CancellationToken))
{
var dialogSet = new DialogSet(accessor);
dialogSet.Add(dialog);
var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
var results = await dialogContext.ContinueDialogAsync(cancellationToken);
if (results.Status == DialogTurnStatus.Empty)
{
await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken);
}
else
{
await dialogContext.ReplaceDialogAsync(dialog.Id, null, cancellationToken);
}
}
}
Jscript code into HTML file.
<script>
function guid() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
var user = {
id: guid().toUpperCase(),
name: 'User-' + Math.floor((1 + Math.random()) * 10000)
};
var botConnection = new BotChat.DirectLine({
token: 'MyToken',
user: user,
webSocket: 'true'
});
const speechOptions = {
speechRecognizer: new BotChat.Speech.BrowserSpeechRecognizer(),
speechSynthesizer: new BotChat.Speech.BrowserSpeechSynthesizer()
};
BotChat.App({
user: user,
botConnection: botConnection,
speechOptions: speechOptions,
bot: { id: 'Bot'+ Math.floor((1 + Math.random()) * 10000)},
resize: 'detect',
}, document.getElementById("bot"));
botConnection
.postActivity({
from: user,
name: 'none',
type: 'event',
value: 'none'
})
.subscribe(function (id) {
console.log('"trigger setUserIdEvent" sent');
});
</script>
Solution 1:[1]
What is _UserStorage
? ... the UserState
class should be utilizing your IStorage
implementation, which it will do automatically since you are already injecting an IStorage
If you just inject the UserState, di will ensure it is created using the IStorage already present: services.AddSingleton<UserState>();
There is not enough here to determine what is going wrong.
Also, what is happening here?
if (member.Id != turnContext.Activity.Recipient.Id)
{
dialogo = await VerificarDialogo();
await IniciarDialogo(turnContext, cancellationToken);
}
Solution 2:[2]
I found the problem, it was logical and not in the code, the problem is my global variable, in net core they are "common" for all instances so all users from diferent instances got the same values in my global variables.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Eric Dahlvang |
Solution 2 | RafaelVenancioGD |