conversation reference (concurrent dictionary become blank) after IIS pool recycle

Viewed 450

We have a bot design in Bot framework-4 using .Net c# sdk. This bot is hosted on IIS and available on different channel such as Directline, MS Teams etc. We want to send proactive messages to all the user in MS teams to notify them irrespective of if they communicated with bot or not. The Proactive messages will be 1:1 message.

After doing lot of R&D we found that we will be only able to send Proactive message to user only when there conversation reference is present. (let me know if other way is also possible.)

Using below link and Sample to send Proactive message to user:

Proactive Message Sample

Document Referred

We are using cosmos DB container and auto save middleware for bot conversation state and user state management.

Code in ConfigureServices method of Startup.cs file:

var blobDbService = botConfig.Services.FirstOrDefault(s => s.Type == ServiceTypes.BlobStorage) ?? throw new Exception("Please configure your Blob service in your .bot file.");
var BlobDb = blobDbService as BlobStorageService;

var dataStore = new AzureBlobStorage(BlobDb.ConnectionString, BlobDb.Container);
var userState = new UserState(dataStore);
var conversationState = new ConversationState(dataStore);
            
services.AddSingleton(dataStore);
services.AddSingleton(userState);
services.AddSingleton(conversationState);
services.AddSingleton<ConcurrentDictionary<string, ConversationReference>>();
services.AddSingleton(new BotStateSet(userState, conversationState));
services.AddBot<EnterpriseTiBOT>(options =>
{
  // Autosave State Middleware (saves bot state after each turn)
    options.Middleware.Add(new AutoSaveStateMiddleware(userState, conversationState));
}

Code to Store Conversation Reference for each user:

private void AddConversationReference(Activity activity)
        {
           
            var conversationReference = activity.GetConversationReference();
            _conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
        }
protected override async Task OnStartAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
        {
            AddConversationReference(dc.Context, cancellationToken);
        }

Code in notifyContoller is same as the code from GitHub Sample. There are 2 issues we are facing :

  1. The concurrent dictionary having conversation reference become blank when the IIS pool is recycled and we are not able to send the proactive message to the user, how to store it in Blob storage and access the same in Notify controller?

  2. We want to send proactive message to all the user whether they have communicated with bot or not, any way to achieve this? Tried 3rd approach from this article. But the challenge is, we are not able to send message to user based on User ID or user principle name.

2 Answers
  1. There are multiple ways to store the conversation and user info. You should store these details in more persistent place rather than in memory. Here is a sample app code which stores user detail and along with the conversation Id in cosmos DB at the time of installation of the app. You could look into the implementation part. It can be any storage (blob, SQL).
  2. For sending proactive message, User must have access to your app. You could make your app install for everyone in the tenant from Teams admin portal. Here is a reference documentation for managing the app from admin portal.

You need to have the conversation ID (between a bot and user) for sending a proactive message. And the conversation Id is created when the bot is installed for an user Or in team/group where user is part of.

I want to add comment to the answer but I do not have enough reputation to do so. Just to eleborate point 1 , if you want to ask where can we get and save the conversation refences, you can get it via method named: OnConversationUpdateActivityAsync.

It took me some time to get to this, so I think it is useful to share. You can get a lot of information eg user ID, channel ID from the activity, here is some sample code:

public class ProactiveBot : ActivityHandler
{
...
...

protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
    {
        var conversationReferences = turnContext.Activity.GetConversationReference();
        
        //this is your user's ID
        string userId = conversationReference.User.Id;
        
       //this is the bot's ID and will be the same for all activities under same bot.
        string botId = conversationReference.Bot.Id;
        ...
        ...
        ...
    }
     
} 
Related