H. Pauwelyn
H. Pauwelyn

Reputation: 14310

clientSecret can not be null while sending a conversation pro-active message to Microsoft Teams

I'll to send a notification pro-active message when something is changed by a bot that is linked to Microsoft Teams. The demo uses (for be simple) only an API endpoint on the bot website.

After I've started a conversation, I store that in the conversation to a property called MemoryVariables.ConversationReferences (type of ConcurrentDictionary<string, ConversationReference>).

When I browse to the API endpoint, then I see that there comes no message however I've got a 200 response. While looking for the bug I've added a breakpoint on marked line.

public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
{
    public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger : base(configuration, logger)
    {
        OnTurnError = async (turnContext, exception) =>
        {
            logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); // <-- breakpoint added here
        };
    }
}

There I see that I've next exception:

System.ArgumentNullException: Value cannot be null.
Parameter name: clientSecret

Here's the stack trace:

   at Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential..ctor(String clientId, String clientSecret)
   at Microsoft.Bot.Connector.Authentication.MicrosoftAppCredentials.<BuildAuthenticator>b__14_0()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
--- End of stack trace from previous location where exception was thrown ---
   at System.Lazy`1.CreateValue()
   at Microsoft.Bot.Connector.Authentication.AppCredentials.GetTokenAsync(Boolean forceRefresh)
   at Microsoft.Bot.Connector.Authentication.AppCredentials.ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Bot.Connector.Conversations.ReplyToActivityWithHttpMessagesAsync(String conversationId, String activityId, Activity activity, Dictionary`2 customHeaders, CancellationToken cancellationToken)
   at Microsoft.Bot.Connector.ConversationsExtensions.ReplyToActivityAsync(IConversations operations, String conversationId, String activityId, Activity activity, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.BotFrameworkAdapter.SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.TurnContext.<>c__DisplayClass22_0.<<SendActivitiesAsync>g__SendActivitiesThroughAdapter|1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.Bot.Builder.TurnContext.SendActivityAsync(IActivity activity, CancellationToken cancellationToken)
   at MyDemoBot.Bot.Controllers.NotifyController.BotCallback(ITurnContext turnContext, CancellationToken cancellationToken) in C:\MyDemoBot\Savaco.KBESAVAC.NotificationBot.Bot\Controllers\NotifyController.cs:line 59
   at Microsoft.Bot.Builder.BotFrameworkAdapter.TenantIdWorkaroundForTeamsMiddleware.OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.MiddlewareSet.ReceiveActivityWithStatusAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.BotAdapter.RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken)

Below you've my NotifyController where the exception is thrown on marked line.

[Route("api/notify")]
[ApiController]
public class NotifyController : ControllerBase
{
    public async Task<IActionResult> Get()
    {
        MemoryVariables memoryVariables = MemoryVariables.GetInstance();
        List<Task> taskList = new List<Task>();
        ICollection<ConversationReference> conversationReferences = memoryVariables.ConversationReferences.Values;

        foreach (ConversationReference conversationReference in conversationReferences)
        {
            taskList.Add(memoryVariables.BotAdapter.ContinueConversationAsync(
                conversationReference.Bot.Id,
                conversationReference,
                BotCallback,
                new CancellationToken()
            ));
        }

        await Task.WhenAll(taskList); // <-- exceptions throws on this line code

        return GetContentResult(new { messagesSend = conversationReferences.Count }, HttpStatusCode.OK);
    }

    private ContentResult GetContentResult(object content, HttpStatusCode httpStatusCode)
    {
        return new ContentResult()
        {
            Content = JsonConvert.SerializeObject(content),
            ContentType = "application/json",
            StatusCode = (int)httpStatusCode
        };
    }

    private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
    {
        await turnContext.SendActivityAsync(
            MessageFactory.SuggestedActions(
                new string[] { "Create A", "Create B" },
                "Hi, you've received a notification. Choose an action from below to continue."
            ),
            cancellationToken
        );
    }
}

Did I miss anything in my code or is there anything specifiek for Microsoft Teams I've missed? Using localhost and the Bot Framework Emulator, I don't have a problem with it.

Upvotes: 1

Views: 1629

Answers (1)

Hilton Giesenow
Hilton Giesenow

Reputation: 10844

So there are a few things to consider here. First of all, storing this in memory is a problem if you app recycles for any reason - you might want to store it more persistently rather (e.g. database). In addition, I'd suggest reading my blog post (new blog engine so it's a bit sparse right now) on How Bots Calls Actually Work as it will help to explain the pieces that you're currently missing.

In essence, if you're not replying directly to an existing message, and you want to send a message at another later time from your bot, then it's referred to as "pro-active messaging" and to do this you need more than just the conversation reference, you need a few other bits of info as well. I've described more about this, with links and sample code, over at Programmatically sending a message to a bot in Microsoft Teams.

Hope that helps

Upvotes: 3

Related Questions