Dependency logging with application insights does not log responses - c#

We have set up application insights with our ASP.NET Core 6 application which uses application insights for logging. We use a RestSharp based HTTP client for executing HTTP requests which works fine.
When trying to debug calls made through the RestSharp library, I am not seeing any response bodies being logged, only the statuscode, while the requests are being (kind of) logged:
{
"name": "AppDependencies",
"time": "2023-02-02T06:05:04.6268266Z",
"tags": {
"ai.application.ver": "1.0.0.0",
"ai.cloud.roleInstance": "MY_NICE_PC",
"ai.user.id": "ltK4V",
"ai.operation.id": "11bf52695a8d8ea19f1cb7573f2b195b",
"ai.operation.parentId": "324234234234",
"ai.operation.name": "POST to/somewhere [v]",
"ai.location.ip": "::1",
"ai.internal.sdkVersion": "rdddsc:2.21.0-429",
"ai.internal.nodeName": "MY_NICE_PC"
},
"data": {
"baseType": "RemoteDependencyData",
"baseData": {
"ver": 2,
"name": "POST /none/of-your/business",
"id": "bfa554335eefae0b",
"data": "https://none.of.your.business/my-nice-api-0/my=1&nice&=2&querystring=3",
"duration": "00:00:04.8666247",
"resultCode": "422",
"success": false,
"type": "Http",
"target": "none.of.your.business",
"properties": {
"DeveloperMode": "true",
"AspNetCoreEnvironment": "localdev",
"_MS.ProcessedByMetricExtractors": "(Name:'Dependencies', Ver:'1.1')"
}
}
}
}
We are using ASP.NET Core 6.0 with the Microsoft.ApplicationInsights.AspNetCore version 2.2.21 and the following DI setup:
services.AddApplicationInsightsTelemetry(configure =>
{
configure.ConnectionString = "MySecretConnectionString";
configure.EnableAdaptiveSampling = false;
});
--- Edit:
I also made an implementation of the ITelemetryInitializer to capture all telemetry for all instances of DependencyTelemetry:
public class DependencyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
// SKIP for now
if (telemetry is DependencyTelemetry dependencyTelemetry)
{
}
}
}
This showed me that for every dependency call only the request is being captured, but the response is not.

That is by design, the dependency call basically logs the outgoing call, as that is what application insights has access to. It logs the duration, and also the response code (422 in your example.) I am also not sure what kind of information you would expect to see from the response.
If you want to get access to the response you can do so in a TelemetryInitializer:
public class CustomInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
if (telemetry is DependencyTelemetry dt
&& dt.Type.Equals("http", System.StringComparison.InvariantCultureIgnoreCase)
&& dt.TryGetHttpResponseOperationDetail(out var response))
{
...
}
}
}
You can then access the headers etc. Technically you can access the response content but since it is a stream by default it can only be read. So reading the full response content can't be done here withouth breaking the application afaik.
Also, even if you could, you should be carefull regarding logging the request and response playload. The size might be large, and Application Insights is not designed for that kind of log data. You might also hit the limits to the lenght of the custom properties and the logged payload will be cut off.

I came up with the following, since it is needed to buffer the response, so that it can be read again, I have added the LoadIntoBufferAsync. Also this seems be available as Async only.
public void Initialize(ITelemetry telemetry)
{
if (telemetry is DependencyTelemetry dependencyTelemetry &&
dependencyTelemetry.Type.Equals(DEPENDENCYTELEMETRY_TYPE_HTTP,
StringComparison.InvariantCultureIgnoreCase)
&& dependencyTelemetry.TryGetHttpResponseOperationDetail(out var response))
{
var task = Task.Run(async () => await response.Content.LoadIntoBufferAsync());
task. Wait();
var stream = response.Content.ReadAsStream();
using var reader = new StreamReader(
stream,
Encoding.UTF8,
detectEncodingFromByteOrderMarks: false,
bufferSize: 512, leaveOpen: true);
var responseBody = reader.ReadToEnd();
dependencyTelemetry.Properties.Add("responseBody", responseBody);
}
}
This only seems to be working with very small stream sizes (response sizes) less than 1K. I

Related

Verify Google pub/sub android payment notification server side C# / ASP.NET MVC

After 3 days I am finished configurations pub/sub push notification from my android app, now my backend server catch every subscription notification made from android app, but now I'm stuck in this stage, all I need is how read full subscription details like(Who make this payment, expire date, ...etc)
I don't know how to do it and what necessary steps to make it happen.
this is what notification look like from my backend server:
{
"message" :
{
"attributes":{ "key": null },
"data": "JSON_CODE",
"messageId": "6542662753109422",
"message_id": "6546652753109422",
"publishTime": "2023-01-01T15:53:42.962Z",
"publish_time": "2023-01-01T15:53:42.962Z"
},
"subscription": "projects/{api_id}/subscriptions/{service_name}-sub"
}
message.data (JSON_CODE) decoded into this:
{
"version": "1.0",
"packageName": "com.expamle.android",
"eventTimeMillis": "1672588422635",
"subscriptionNotification":
{
"version": "1.0",
"notificationType": 4,
"purchaseToken": "lkgkfeofbmfnnalianjdppej.AO-J1OxNcztkkzntpvQk4nttaBiqHJ5WMD58tb_KxCdsyPooE_QPvqXdtnoEpqD0t96j5V4lxol3_FfpuRNDvBeRTYKo_ixJtw",
"subscriptionId": "12_month_plan"
}
}
I have a keys.json file.
And I made all permission required in my pub/sub service accounts.
How to read the full subscription details?
I solved it, to any new coming users this is how to solve it .
first I've used simple .NET library "Google.Apis.Auth.OAuth2" to get access token to any query request.
by this simple code I can get token using key.json i've downloaded from my google cloud console from "**https://console.cloud.google.com/iam-admin/serviceaccounts**" and by manage keys you can generate json secret key.
Auth:
string keyFile = HttpContext.Server.MapPath("~/bin/key.json");
var credential = GoogleCredential.FromFile(keyFile).CreateScoped(new[] { "https://www.googleapis.com/auth/androidpublisher" });
string token = await credential.UnderlyingCredential.GetAccessTokenForRequestAsync();
}
Now you can request google query GET url to get full purchased subscription info .
https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{PACKAGE_NAME}/purchases/subscriptionsv2/tokens/{purchaseTokenComesWithMessage.data}
any requests must be included with Authorization Bearer accessToken we'ave generated from Auth. section above.
the result:
{
"kind": "androidpublisher#subscriptionPurchaseV2",
"startTime": "2023-01-03T14:56:24.940Z",
"regionCode": "US",
"subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
"latestOrderId": "GPA.3394-8372-7793-03673",
"testPurchase": {},
"acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
"externalAccountIdentifiers": {
"obfuscatedExternalAccountId": "92d8f330-8d2a-45ab-b6a1-6720b3d15999"
},
"lineItems": [
{
"productId": "12_month_plan",
"expiryTime": "2023-01-03T15:28:21.928Z",
"autoRenewingPlan": {
"autoRenewEnabled": true
},
"offerDetails": {
"basePlanId": "360day",
"offerTags": [
"tools"
]
}
}
] }
Thanks

AWS HTTP API Gateway integration with SQS - MessageAttribute doesn't work

I've integrated HTTP API Gateway with SQS and have some problem with MessageAttributes. The main goal is to pass parameter in URL, attach it to message attribute and send to SQS.
I've created HTTP API with RouteKey = "$default" with the following RequestParameters:
RequestParameters =
{
{"MessageBody", "$request.body"},
{"QueueUrl", Output.Format($"{config.SqsPath}")},
{"MessageAttributes", #"{
""SubscriptionId"": {
""DataType"": ""String"",
""StringValue"": ""abc123""
}
}"}
}
for test purpose I've hardcoded value, but at the end I'll use $request.querystring.SubscriptionId
What I have now, is the following:
AWS Console printscreen
It looks good, however all post requests which I made didn't contains any attributes:
CloudWatch printscreen
What have I missed?
Update
I figured out what happened - I had to pass parameters template with invalid format (however, no error was thrown).
Finally I have a working version, message attribute in AWS Console:
{ "SubscriptionId": { "DataType": "String", "StringValue": "${request.querystring.SubscriptionId}" } }
and my integration code:
RequestParameters =
{
{"MessageBody", "$request.body"},
{"QueueUrl", Output.Format($"{config.SqsPath}")},
{"MessageAttributes", #"{ ""SubscriptionId"": { ""DataType"": ""String"", ""StringValue"": ""${request.querystring.SubscriptionId}"" } }"}
},

Datadog logs with C# and .NET

I am trying to post Datadog-logs from my C# application. I managed to send my logs with the desired structure using Postman, but I just can't figure out how to achieve the same goal from the code.
What I tried:
Using DogStatsD - but I don't want to install an agent, I'd much rather use the Datadog REST API to just post my logs.
Using Serilog.Sinks.Datadog.Logs - Which seems to be pretty easy to use, but I can't figure out how this works, and whether it is possible to change the log structure or not. By default, there are MessageTemplate and Properties fields in the resulting json. I'd like to be able to send my own structure in one message, rather then use the MessageTemplate. Is that possible?
The desired log to be seen in Datadog UI Logs Section:
{
hostname: myHost
myStuff {
item1: item_val1
item2: item_val2
}
otherStuff: oh wow this is cool
service: MyService
}
Here's what I sent using Postman to achieve this result:
URL: https://http-intake.logs.datadoghq.com/v1/input
Headers:
DD-API-KEY: my_api_key
Content-Type: application/json
Body:
{
"ddsource": "mySource",
"ddtags": "myTag: myVal, myValWithoutTag",
"hostname": "myHost",
"message": {
"myStuff":
{
"item1": "item_val1",
"item2": "item_val2"
},
"otherStuff": "oh wow this is cool"
},
"service": "MyService"
}
Is it possible to achieve the same (or even similar) result using datalog serilog sinks? If not, how can I achieve this result in C#?
Here is what I tried from the code:
var config = new DatadogConfiguration(url: "intake.logs.datadoghq.com", port: 443, useSSL: true, useTCP: true);
using (var log = new LoggerConfiguration().WriteTo.DatadogLogs(
"<myApiKey>",
source: "mySource",
service: "myService",
host: "myHost",
tags: new string[] {"myTag:myVal", "myValWithoutTag"},
configuration: config
).
CreateLogger())
{
var messageTemplate = "{message}";
var message = new
{
myStuff = new
{
item1 = "item_val1",
item2 = "item_val2"
}
};
log.Information(messageTemplate, message);
}
With the undesired result in Datadog UI Logs section:
{
host: myHost
level: Information
MessageTemplate: {message}
Properties: {
message: { myStuff = { item1 = item_val1, item2 = item_val2 } }
}
service: myService
Timestamp: 2021-05-17T00:13:14.2614896+03:00
}
The tags part did work, and also the host and service parts are the same. I don't mind the level and Timestamp additions,
but I'd love to change the body to behave like in the Postman example (just the message as JSON).
So my questions are:
Is it possible to control the message body format using Datadog Serilog sinks?
Is there any good alternative that I didn't try? (except for writing my own client, which is what I'm leaning towards)
Can anyone explain to me how it works? I can't figure out the concept of the sink. Can anyone explain how it works? And why is there no actual REST HTTP client for this task?
Thanks!
You really no need use Agent for sending and customizing logs to Datadog. Its pretty cool for providing traces and other metrics. Unfortunately, MessageTemplate could`t be changed in Datadog logging configuration. Only put MessageTemplate like message in Serilog:
var message = "Service started";
logger.Information(message);
Properties you can add 2 ways.
Using Serilog Enrich:
var config = new DatadogConfiguration(url: "intake.logs.datadoghq.com", port: 443, useSSL: true, useTCP: true);
using (var log = new LoggerConfiguration()
.Enrich.WithProperty(item1, item_val1)
.WriteTo.DatadogLogs(
"<myApiKey>",
source: "mySource",
service: "myService",
host: "myHost",
tags: new string[] {"myTag:myVal", "myValWithoutTag"},
configuration: config
).
CreateLogger())
Or push property in LogContext by middleware:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Serilog.Context;
namespace MyProject.Middlewares
{
public class LogPropertyMiddleware
{
private readonly RequestDelegate _next;
public LogUserInfoMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
LogContext.PushProperty("item1", "item_val1");
await _next.Invoke(context);
}
}
}
Add middleware to startup:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
.....
app.UseMiddleware<LogPropertyMiddleware>();
.....
}

Operation returned an invalid status code 'Forbidden' when calling GetConversationMembersAsync for 1:1 private conversation

Using the "Hello World" Microsoft Teams application sample from here: https://github.com/OfficeDev/msteams-samples-hello-world-csharp
Trying to get a list of participants in a personal Microsoft Teams 1:1 chat after an action command from a messaging extension is invoked. Specifically, I need the e-mail address of the other participant, where I am the first participant.
This is the code from the messages controller:
[BotAuthentication]
public class MessagesController : ApiController
{
[HttpPost]
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
using (var connector = new ConnectorClient(new Uri(activity.ServiceUrl)))
{
if (activity.IsComposeExtensionQuery())
{
// Invoke the command handler
var response = await MessageExtension.HandleMessageExtensionQuery(connector, activity).ConfigureAwait(false);
return response != null
? Request.CreateResponse<ComposeExtensionResponse>(response)
: new HttpResponseMessage(HttpStatusCode.OK);
}
else
{
await EchoBot.EchoMessage(connector, activity);
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
}
}
}
The code from MessageExtension.HandleMessageExtensionQuery is as follows:
public static async Task<ComposeExtensionResponse> HandleMessageExtensionQuery(ConnectorClient connector, Activity activity)
{
var query = activity.GetComposeExtensionQueryData();
if (query == null)
{
return null;
}
// Exception thrown here - error 403, there is no additional data except "Operation returned an invalid status code 'Forbidden'"
var members = await connector.Conversations.GetConversationMembersAsync(activity.Conversation.Id);
var handler = GetCommandHandler(query.CommandId); // Gets a handler based on the command, irrelevant for this question
if (handler == null)
{
return null;
}
return await handler.HandleCommand(query, members); // Should handle the command, but never comes here if we are in a 1:1 conversation
}
The call to GetConversationMembersAsync fails with the following message: Operation returned an invalid status code 'Forbidden' if the command is invoked from a 1:1 personal conversation between two people.
The call doesn't fail if invoked from a group channel.
How to get the list of participants of a 1:1 personal conversation? Do I have to authenticate my user through the bot in order to do that, or do I have to grant my bot some particular permissions? Does my account need to have some particular permissions in order to do this?
EDIT - Added the app manifest
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5",
"version": "1.0.0",
"id": "1ce95960-0417-4469-ab77-5052758a4e7e",
"packageName": "com.contoso.helloworld",
"developer": {
"name": "Contoso",
"websiteUrl": "https://8112abe3.ngrok.io",
"privacyUrl": "https://8112abe3.ngrok.io/privacy-policy",
"termsOfUseUrl": "https://8112abe3.ngrok.io/terms-service"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "Hello World",
"full": "Hello World App"
},
"description": {
"short": "Hello World App for Microsoft Teams",
"full": "This sample app provides a very simple app. You can extend this to add more content and capabilities."
},
"accentColor": "#60A18E",
"configurableTabs": [
{
"configurationUrl": "https://526d7c43.ngrok.io/configure",
"canUpdateConfiguration": true,
"scopes": [
"team",
"groupchat"
]
}
],
"staticTabs": [
{
"entityId": "com.contoso.helloworld.hellotab",
"name": "Hello Tab",
"contentUrl": "https://8112abe3.ngrok.io/hello",
"websiteUrl": "https://8112abe3.ngrok.io/hello",
"scopes": [
"personal"
]
}
],
"bots": [
{
"botId": "bfbcb607-5c29-4438-85a5-15e63fb0b273",
"scopes": [
"personal",
"team",
"groupchat"
],
"supportsFiles": false,
"isNotificationOnly": false
}
],
"composeExtensions": [
{
"botId": "bfbcb607-5c29-4438-85a5-15e63fb0b273",
"canUpdateConfiguration": true,
"commands": [
{
"id": "getRandomText",
"type": "query",
"title": "Get random text",
"description": "",
"initialRun": true,
"fetchTask": false,
"context": [
"commandBox",
"compose",
"message"
],
"parameters": [
{
"name": "cardTitle",
"title": "Subject",
"description": "",
"inputType": "text"
}
]
}
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"8112abe3.ngrok.io"
]
}
EDIT 2 - After trying, based on sample 51 - TeamsMessagingExtensionsAction
As suggested, I tried with the sample 51 called "TeamsMessagingExtensionsAction"
The code is:
MicrosoftAppCredentials.TrustServiceUrl(turnContext.Activity.ServiceUrl);
var members = (await turnContext.TurnState.Get<IConnectorClient>().Conversations.GetConversationMembersAsync(
turnContext.Activity.Conversation.Id).ConfigureAwait(false)).ToList();
The exception along with the stack trace:
Microsoft.Bot.Schema.ErrorResponseException: Operation returned an invalid status code 'Forbidden'
at Microsoft.Bot.Connector.Conversations.GetConversationMembersWithHttpMessagesAsync(String conversationId, Dictionary`2 customHeaders, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Connector\Conversations.cs:line 1462
at Microsoft.BotBuilderSamples.Bots.TeamsMessagingExtensionsActionBot.ShareMessageCommand(ITurnContext`1 turnContext, MessagingExtensionAction action) in D:\Visual Studio Projects\botbuilder-samples\samples\csharp_dotnetcore\51.teams-messaging-extensions-action\Bots\TeamsMessagingExtensionsActionBot.cs:line 68
at Microsoft.BotBuilderSamples.Bots.TeamsMessagingExtensionsActionBot.OnTeamsMessagingExtensionSubmitActionAsync(ITurnContext`1 turnContext, MessagingExtensionAction action, CancellationToken cancellationToken) in D:\Visual Studio Projects\botbuilder-samples\samples\csharp_dotnetcore\51.teams-messaging-extensions-action\Bots\TeamsMessagingExtensionsActionBot.cs:line 29
at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnTeamsMessagingExtensionSubmitActionDispatchAsync(ITurnContext`1 turnContext, MessagingExtensionAction action, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 201
at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnInvokeActivityAsync(ITurnContext`1 turnContext, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 88
at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 39
at Microsoft.Bot.Builder.BotFrameworkAdapter.TenantIdWorkaroundForTeamsMiddleware.OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\BotFrameworkAdapter.cs:line 1158
at Microsoft.Bot.Builder.MiddlewareSet.ReceiveActivityWithStatusAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\MiddlewareSet.cs:line 55
at Microsoft.Bot.Builder.BotAdapter.RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\BotAdapter.cs:line 182
EDIT 3 - Tried with the sample 57
So I have started with the sample 57 called "TeamsConversationBot" and TLDR; works in channel, doesn't work in a private conversation.
Here are the steps performed:
Note: ngrok was already started using the command ngrok http -host-header=rewrite 3978 and my existing bot registration was already configured to listen on the given URL
Updated the appsettings.json configuration for the project to use the Microsoft App Id and App Password from the Bot Framework registration.
Edited the manifest.json contained in the TeamsAppManifest with the required GUIDs, zipped it along with PNG icons into manifest.zip and uploaded to Teams App Studio using "Import an existing app", which resulted in a new application called "TeamsConversationBot"
In Teams App Studio, opened the new application for editing, went to "Test and distribute", clicked "Install" and then "Add" on the next screen.
Navigated to the above screen again, but instead of "Add" I chose "Add to a team" from the drop down, where I added the bot to the channel of the Team I am the owner of.
In Visual Studio, went to TeamsConversationsBot.cs and set a breakpoint in MessageAllMembersAsync method, on a line which says var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken);
Started the project, went to my channel in Teams, sent the "MessageAllMembers" to my bot, waited for the above mentioned breakpoint to be hit and observed that the said call succeeds, i.e. returns a list of members. So far so good!
Went back to Teams App Builder to edit my "TeamsConversationsBot" app.
In manifest editor, went to "Messaging extensions" tab and set up my existing bot to listen for messaging extension invocations.
Added a new action-based command with a defined number of parameters. Gave it an Id, named a parameter and input the other mandatory fields, doesn't really matter. For "select the context in which the compose extension should work" I chose "Command Box" and "Compose Box", ticked "Initial run" and clicked "Save".
Installed the app again using "Test and distribute" -> "Install" and then "Add" button.
In Visual Studio, added the following method to TeamsConversationBot.cs:
protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
var connector = turnContext.TurnState.Get<IConnectorClient>();
var conversation = turnContext.Activity.Conversation;
var members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken);
return await base.OnInvokeActivityAsync(turnContext, cancellationToken);
}
Set a breakpoint on the line which says var members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken); and start the project
Went to the channel, selected messaging extensions under the compose box and invoked the newly added command. This triggered the above breakpoint to be hit. Performed a step over and observed that members variable contained all members of the channel. So that works too!
Went to a private conversation between me and the other team member (where me and them both have sent messages to each other previously, to ensure the conversation is not empty), selected messaging extensions under the compose box and invoked the newly added command. This again triggered the above mentioned breakpoint to be hit. Performed a step over and bam! The call has caused an unhandled exception Microsoft.Bot.Schema.ErrorResponseException: Operation returned an invalid status code 'Forbidden'
Response to Edit 3
Sorry, it looks like I misunderstood what you were trying to do. You cannot do this with the Bot Framework. Technically, the bot is not part of a Personal conversation between you and another user. Therefore, it doesn't have permissions to get information about that conversation, even though it's available as a Messaging Extension.
You may be able to use this Graph API call, although you'll have to deal with getting the the auth token on your own:
https://graph.microsoft.com/beta/chats/<conversationId>/members
Making this an answer because it's too long to comment and I think it should accomplish what you need. Please let me know if this works or not and I can edit this
I didn't see anything on the backend by looking into your appId, so I'm not sure of what the actual cause of the problem is.
You shouldn't need any permissions set at all -- those are only for OAuth, which GetConversationMembersAsync doesn't need. You should only really need the scopes set in your manifest.json, which appear to be set just fine.
However, for your App Registration, you may need to ensure that this is checked, under Authentication:
If it isn't you can manually adjust your App Registration's Manifest, by setting this:
"signInAudience": "AzureADandPersonalMicrosoftAccount",
If none of that works:
Please try Sample 57, which has a GetMembersAsync function that accomplishes the same thing, but with the current BotFramework SDK. Just be sure to follow the README and set everything up as specified.
Let me know how it goes. If you need additional help, can you try explaining your reproduction steps in as much detail as possible? I'd like to try to reproduce this and want to make sure our steps match--it may also help me call out where you might be going wrong.
Actually, there is a way to get the list of participants in a 1:1 private conversation between two people when invoking an action-based messaging extension. The trick is to get the bot into the private conversation too.
I thought about that solution in the beginning but didn't know how, and since no one suggested it, I thought it wasn't possible in one go. But turns out I was wrong.
So in case anyone ever stumbles upon this kind of problem. here it is.
Originally I did this using a FetchTask action messaging extension (the action-based messaging extension where you don't supply the predefined list of parameters, but fetch the parameters using the bot).
But it can be done using a regular action with static parameters too, so let's make an example using the method OnInvokeActivityAsync that I used in the question. See comments in the code below for an explanation.
protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
var connector = turnContext.TurnState.Get<IConnectorClient>();
var conversation = turnContext.Activity.Conversation;
IList<ChannelAccount> members;
try
{
members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken);
}
catch (ErrorResponseException ex)
{
// If the ErrorResponseException contains the response with status code 403, that means our bot is not a member of this conversation.
// In that case, return an adaptive card containing the prompt to add the bot to the current conversation.
// After accepting the prompt, the bot will be added to the conversation and we will be able to obtain the list of conversation participants.
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
{
return new InvokeResponse
{
Status = 200,
Body = AddBotToConversation()
};
}
throw;
}
// At this point, we have the list of conversation members
var otherMember = members.FirstOrDefault(x => x.Id != turnContext.Activity.From.Id);
return new InvokeResponse
{
Status = 200,
Body = await DoSomethingWithOtherMemberInformationAndReturnACard(otherMember, cancellationToken)
};
}
The AddBotToConversation can be defined like this:
private MessagingExtensionActionResponse AddBotToConversation()
{
var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0))
{
Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock("We need to add the bot to this conversation in order to perform the requested action"),
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction
{
Title = "Continue",
Data = new Dictionary<string, object>
{
// The magic happens here. This tells Teams to add this bot to the current conversation
["msteams"] = new Dictionary<string, bool>
{
["justInTimeInstall"] = true,
}
}
}
}
};
var invokeResponse = new MessagingExtensionActionResponse
{
Task = new TaskModuleContinueResponse
{
Value = new TaskModuleTaskInfo
{
Card = new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = card
}
}
}
};
return invokeResponse;
}
Just make sure the messaging extension is action-based, not search-based. Search extensions will not support this approach.
EDIT Last but not least, don't forget to add the "groupchat" scope to the "scopes" collection of your bot under the "bots" collection in the Teams App manifest.json file, or else you won't be able to add your bot to a private conversation.

PATCH request not recognised by Marvin.JsonPatch

I'm trying to send an AJAX PATCH request to a Web API method and have the patched object recognised by Marvin.JsonPatch.
So far, everything I've sent to the server has resulted in an empty request being received.
The Web API controller method looks like this:
public async Task<IHttpActionResult> Update(long orderId, JsonPatchDocument<Order> patchedOrder)
I'm POSTing using an HttpClient like this (can't use async in this application)...
var patchDoc = new JsonPatchDocument<Order>();
patchDoc.Replace(e => e.Price, newPrice);
patchDoc.Replace(e => e.WordCount, newWordCount);
var request = new HttpRequestMessage(new HttpMethod("PATCH"), uri)
{
Content = new StringContent(JsonConvert.SerializeObject(patchDoc),
System.Text.Encoding.Unicode,
"application/json")
};
HttpResponseMessage response;
using (var client = new HttpClient(...))
{
response = client.SendAsync(request).GetAwaiter().GetResult();
}
But when the controller is it, the patchedOrder argument is null.
While debugging on the controller I've also tried
var s = await Request.Content.ReadAsStringAsync();
But this returns an empty string - can anyone explain why?
UPDATE:
This is what the contents of the JsonPatch document look like when passed in to the HttpClient...
{
"Operations": [{
"OperationType": 2,
"value": 138.7,
"path": "/price",
"op": "replace"
},
{
"OperationType": 2,
"value": 1320,
"path": "/wordcount",
"op": "replace"
}],
"ContractResolver": {
"DynamicCodeGeneration": true,
"DefaultMembersSearchFlags": 20,
"SerializeCompilerGeneratedMembers": false,
"IgnoreSerializableInterface": false,
"IgnoreSerializableAttribute": true,
"NamingStrategy": null
},
"CaseTransformType": 0
}
Somewhere during the development of Marvin.JsonPatch, the JsonPatchDocument<T> was annotated with an attribute that applied a custom JSON serializer:
[JsonConverter(typeof(JsonPatchDocumentConverter))]
This converter enables you to call JsonConvert.SerializeObject() on such a patch document and actually generate a patch document, as opposed to a representation of the JsonPatchDocument<T> CLR object.
Upgrade Marvin.JsonPatch and Newtonsoft.Json to the latest verison, and serialization should succeed.

Categories

Resources