I have a requirement that a Bot message posted to MS Teams should expire after a timeout period. To achieve this I am calling UpdateAsyncActivity() on a separate thread after a timeout, however this fails with a NullReferenceException:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.Bot.Builder.BotFrameworkAdapter.UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken)
at Microsoft.Bot.Builder.TurnContext.<>c__DisplayClass31_0.<<UpdateActivityAsync>g__ActuallyUpdateStuff|0>d.MoveNext()--- End of stack trace from previous location where exception was thrown ---
at Microsoft.Bot.Builder.TurnContext.UpdateActivityInternalAsync(Activity activity, IEnumerable`1 updateHandlers, Func`1 callAtBottom, CancellationToken cancellationToken)
at Microsoft.Bot.Builder.TurnContext.UpdateActivityAsync(IActivity activity, CancellationToken cancellationToken)
at NotifyController.ClearCard(ITurnContext turnContext, Activity timeoutActivity, Int32 timeoutInMinutes) in NotifyController.cs:line 48
The code looks something like this:
[Route("api/notify")]
[ApiController]
public class NotifyController : ControllerBase
{
private IBotFrameworkHttpAdapter Adapter;
private readonly string _appId;
private readonly IConversationStorage _conversationStorage;
public NotifyController(IBotFrameworkHttpAdapter adapter, IConfiguration configuration, IConversationStorage conversationStorage)
{
Adapter = adapter;
_appId = configuration["MicrosoftAppId"] ?? string.Empty;
_conversationStorage = conversationStorage;
}
[HttpPost]
public async Task<IActionResult> PostForm([FromBody] RestData restData)
{
ConversationReference conversationReference =
_conversationStorage.GetConversationFromStorage(restData.ConversationId);
await ((BotAdapter)Adapter).ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
await BotCallback(restData.AdaptiveCard, context, token), default(CancellationToken));
return new ContentResult();
}
private async Task BotCallback(string adaptiveCard, ITurnContext turnContext, CancellationToken cancellationToken)
{
var activity = MessageFactory.Attachment(adaptiveCard.ToAttachment());
ResourceResponse response = await turnContext.SendActivityAsync(activity);
var timeoutActivity = turnContext.Activity.CreateReply();
timeoutActivity.Attachments.Add(AdaptiveCardExamples.TimeoutTryLater.ToAttachment());
timeoutActivity.Id = response.Id;
// SUCCESS
//Thread.Sleep(10000);
//await turnContext.UpdateActivityAsync(timeoutActivity);
// FAIL
Thread CardClearThread = new Thread(() => ClearCard(turnContext, timeoutActivity));
CardClearThread.Start();
}
private async void ClearCard(ITurnContext turnContext, Activity timeoutActivity)
{
Thread.Sleep(10000);
await turnContext.UpdateActivityAsync(timeoutActivity);
}
}
I want the timeout to happen on a separate thread so that PostForm()
returns a response as soon as the original message is sent.
I presume what is happening is that some aspect of turnContext
is being disposed when the main thread returns, causing the sleeping thread to fail when it wakes up.
Is there a solution/alternative approach to this?
With in some mins , Bot has to response to the team otherwise this issue get popup best solution for this to implement proactive message concept