botframework-sdk: Can't send custom Slack Message with version 3 of Bot Connector

I have a bot that replies with a message consisting only of attachments. When it works on Slack, it uses Slack attachment formatting quite heavily, thus I have to use ChannelData property.

In version 1 of BotConnector, the code was like this

    var reply = message.CreateReplyMessage();
    reply.Attachments = new List<Attachment>();
    var attachments = new List<object>(); //Slack-formatted attachments
    //filling attachments...
    reply.ChannelData = new {attachments};

and it worked. Now, in version 3 code has changed to

    var reply = activity.CreateReply();
    reply.Attachments = new List<Attachment>();
    var attachments = new List<object>(); //Slack-formatted attachments
    //filling attachments...
    reply.ChannelData = new {attachments};

    var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
    await connector.Conversations.ReplyToActivityAsync(reply);

which, basically, boils down to using different method to create reply, and another one to send the reply back.

Now, the problem is, that I don’t get the reply back to Slack. Diagnostics in AppInsight show me that somewhere in Connector something like this happens:

Exception type: System.ArgumentNullException
Failed method: SlackChannel.SlackMapper+d__5.MoveNext
Exception Message: value cannot be null. Parameter name: source
ChannelData: {}
message: Invalid ChannelData

Please note that ChannelData in this diagnostics seems to be empty. So what I gather from all this is that something has changed in the way BotConnector processes ChannelData. How can I find out what exactly am I doing wrong?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 18

Most upvoted comments

…and now I’ve found why that happens. Here the culprit:

SerializationSettings = new JsonSerializerSettings
{ 
    //...
    ContractResolver = new ReadOnlyJsonContractResolver(),
};

ReadOnlyJsonContractResolver skips all read-only properties, and the properties in anonymous classes are read-only. If I switch to “regular” class for my Slack message, it works just fine.

Now, the problem is that it’s quite cumbersome to have regular class for every channel… so is this intentional or not?

Minimal repro is actually quite easy:

reply.ChannelData = new {text = "here should be attachments"};
connector.Conversations.ReplyToActivityAsync(reply);
  1. JsonConvert.SerializeObject(reply) shows

     "channelData": {"text": "here should be attachments" }
    
  2. IServiceClientTracingInterceptor logs EnterMethod for ReplyToActivity with

     ChannelData: {text: "here should be attachments"}
    
  3. IServiceClientTracingInterceptor logs SendRequest with

     "channelData": {}
    
  4. delegating handler logs

     "channelData": {}
    

It boils down (I think, at least) to this line in Microsoft.Bot.Connector.Conversations.ReplyToActivityWithHttpMessagesAsync:

  _httpRequest.Content = (HttpContent) new StringContent(SafeJsonConvert.SerializeObject((object) activity, this.Client.SerializationSettings), Encoding.UTF8);

which for some reason serializes channelData as empty object.