C
C#5mo ago
senkd

Graph API - HttpClient returns 404, but curl (and Graph Explorer) for same request works?

I'm completely lost. I've resorted to just calling curl from C# as this is completely non-sensical to me. I started with using the Graph SDK, which I had multiple issues with, and I also received a "Not Found" error when trying to use graphClient.Groups[groupId].Threads[ThreadId].PostAsync(body) My current code looks like this:
c#

var token = await GetUserTokenAsync(); //gets the latest token from the GraphClient

using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
using (var content = new StringContent(body))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

var postUrl = new UriBuilder($"https://graph.microsoft.com/v1.0/groups/{groupId}/threads/{threadId}/reply").ToString();

var resp = await client.PostAsync(postUrl, content);

if (!resp.IsSuccessStatusCode)
{
//same Url and body
Console.WriteLine($"Error: Unable to post reply to group {groupId} thread {threadId}. The server said {await resp.Content.ReadAsStringAsync()}");
String curlCommand = $"curl -i -X POST -H \"Authorization: Bearer {token}\" -H \"Content-Type: application/json\" -d '{body}' \"https://graph.microsoft.com/v1.0/groups/{groupId}/threads/{threadId}/reply\"";
Console.WriteLine($"Try this with curl: {curlCommand}");
}
}
}
c#

var token = await GetUserTokenAsync(); //gets the latest token from the GraphClient

using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
using (var content = new StringContent(body))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

var postUrl = new UriBuilder($"https://graph.microsoft.com/v1.0/groups/{groupId}/threads/{threadId}/reply").ToString();

var resp = await client.PostAsync(postUrl, content);

if (!resp.IsSuccessStatusCode)
{
//same Url and body
Console.WriteLine($"Error: Unable to post reply to group {groupId} thread {threadId}. The server said {await resp.Content.ReadAsStringAsync()}");
String curlCommand = $"curl -i -X POST -H \"Authorization: Bearer {token}\" -H \"Content-Type: application/json\" -d '{body}' \"https://graph.microsoft.com/v1.0/groups/{groupId}/threads/{threadId}/reply\"";
Console.WriteLine($"Try this with curl: {curlCommand}");
}
}
}
and when I run the generated curl command... it works? Exact same url, exact same token, exact same content type... but curl (and Graph Explorer) works, and HttpClient doesn't? What in the world am I missing?
1 Reply
senkd
senkd5mo ago
Ok, well, it seems like it was an issue related to timing...
c#
var token = await GetUserTokenAsync();

using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
using (var content = new StringContent(body, Encoding.UTF8, "application/json"))
{
var postUrl = $"https://graph.microsoft.com/v1.0/groups/{groupId}/threads/{threadId}/reply";

var resp = await client.PostAsync(postUrl, content);

if (resp.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("Info: Retrying post-reply (after 15 seconds), Graph returned 404...");
await Task.Delay(TimeSpan.FromSeconds(15));
resp = await client.PostAsync(postUrl, content);
}
if (resp.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("Info: Graph still says this doesn't exist. Waiting 45 more seconds and trying again...");
await Task.Delay(TimeSpan.FromSeconds(45));
resp = await client.PostAsync(postUrl, content);
}
if (resp.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("Info: Graph is really sure that this doesn't exist, but we'll try again in 60 seconds...");
await Task.Delay(TimeSpan.FromSeconds(60));
resp = await client.PostAsync(postUrl, content);
}
if (!resp.IsSuccessStatusCode)
{
Console.WriteLine($"Error: Unable to post reply to group {groupId} thread {threadId}. The server said {await resp.Content.ReadAsStringAsync()}");
String curlCommand = $"curl -i -X POST -H \"Authorization: Bearer {token}\" -H \"Content-Type: application/json\" -d '{body}' \"{postUrl}\"";
Console.WriteLine($"Try this with curl: {curlCommand}");
}
Console.WriteLine("Posted!");
}
}
c#
var token = await GetUserTokenAsync();

using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
using (var content = new StringContent(body, Encoding.UTF8, "application/json"))
{
var postUrl = $"https://graph.microsoft.com/v1.0/groups/{groupId}/threads/{threadId}/reply";

var resp = await client.PostAsync(postUrl, content);

if (resp.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("Info: Retrying post-reply (after 15 seconds), Graph returned 404...");
await Task.Delay(TimeSpan.FromSeconds(15));
resp = await client.PostAsync(postUrl, content);
}
if (resp.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("Info: Graph still says this doesn't exist. Waiting 45 more seconds and trying again...");
await Task.Delay(TimeSpan.FromSeconds(45));
resp = await client.PostAsync(postUrl, content);
}
if (resp.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("Info: Graph is really sure that this doesn't exist, but we'll try again in 60 seconds...");
await Task.Delay(TimeSpan.FromSeconds(60));
resp = await client.PostAsync(postUrl, content);
}
if (!resp.IsSuccessStatusCode)
{
Console.WriteLine($"Error: Unable to post reply to group {groupId} thread {threadId}. The server said {await resp.Content.ReadAsStringAsync()}");
String curlCommand = $"curl -i -X POST -H \"Authorization: Bearer {token}\" -H \"Content-Type: application/json\" -d '{body}' \"{postUrl}\"";
Console.WriteLine($"Try this with curl: {curlCommand}");
}
Console.WriteLine("Posted!");
}
}
The Graph API will return a conversationThreadId immediately on creation, but it can take up to 2 minutes (from my testing) before the replies can be posted. From my testing only ~10 out of ~2000 replies needed to wait the full 2 minutes. Many worked immediately, some needed 15 seconds, others needed 1 minute. It would've been great if I didn't need to go through all of this manual effort to figure this out...