Send file attachment to Facebook messenger API in C# - c#

I looked at the messenger documentation at https://developers.facebook.com/docs/messenger-platform/send-messages/#file to try and figure out how to send local attachments. However, when I try it out with a httpclient I get an error saying that the message body can not be empty must provide a valid attachment or message. Below is my code
string fileType = ImageExtensions.Contains(Path.GetExtension(url).ToUpper()) ? "image" : "file";
var multipartContent = new MultipartFormDataContent();
var content = new StringContent($"{{\"attachment\":{{\"type\":\"{fileType}\", \"payload\":{{\"is_reusable\"=true}}}}");
multipartContent.Add(new StringContent($"{{\"id\":\"{long.Parse(recipient)}\"}}"), "recipient");
multipartContent.Add(new StringContent($"{{\"attachment\":{{\"type\":\"{fileType}\", \"payload\":{{\"is_reusable\"=true}}}}"), "message");
var file1 = File.ReadAllBytes(url);
var file2 = new ByteArrayContent(file1);
file2.Headers.Add("Content-Type", GetMimeType(Path.GetExtension(url)));
multipartContent.Add(file2,"filedata", Path.GetFileName(url));
request.Content = multipartContent;
The file type is image and the mime type is image/jpeg. I know the url exists as I checked File.exists

Welcome to Stack Overflow!
From the code sample provided it's quite hard to work out what's going wrong as it's not runnable in isolation.
That said, I'd first verify that the image you're consuming by url exists in the format you expect. If you download the .jpeg from the url can you open it on your machine? Assuming it's well structured I'd then try and work out if it's the HttpClient that's malformed - or if there's something wrong with the values you're providing to the Facebook API.
You can do this by creating a simple C# Web API project that listens for a multipart upload on a specific route. See this answer for some sample code on how to do this.
Assuming that you're able to send the .jpeg in question between a local client and a local endpoint accepting a multipart/form-data header then the issue must be with how you're using the Facebook API itself.
In your sample I don't see you using the value of the content variable anywhere. Is this intentional?
If that missing variable is a red-herring then you could try running something along the lines of (making sure to swap the values out as necessary for the ones you're having problems with):
using (var httpClient = new HttpClient())
using (var formDataContent = new MultipartFormDataContent())
{
// Read the file in from a local path first to verify that the image
// exists in the format you're expecting.
var fileStream = File.OpenRead("/some/path/image.jpeg");
using (var streamContent = new StreamContent(fileStream))
{
// Don't actually call `.Result` you should await this, but for ease of
// demonstration.
var imageContent = new ByteArrayContent(streamContent.ReadAsByteArrayAsync().Result);
imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
formDataContent.Add(imageContent, "image", "your-file-name-here");
formDataContent.Add(new StringContent ($"{{\"id\":\"{long.Parse("your-recipient-here")}\"}}"), "recipient");
formDataContent.Add(new StringContent($"{{\"attachment\":{{\"type\":\"{"image"}\", \"payload\":{{\"is_reusable\"=true}}}}"), "message");
// Again don't call `.Result` - await it.
var response = httpClient.PostAsync("https://some-url-here.com", formDataContent).Result;
}
}
If you run the above do you still get an empty message body error?

If you stumble upon this and you're using the FacebookClient from Nuget. This is how I finally managed to upload files to the messages api using vb.net.
Dim Client = New FacebookClient(GetPageAccessToken(PageID))
Dim TmpParams As New Dictionary(Of String, Object)
TmpParams.Add("recipient", "{""id"":""RECIPIENT_ID""}")
TmpParams.Add("message", "{""attachment"":{""type"":""image"", ""payload"":{""url"":"""", ""is_reusable"":false}}}")
TmpParams.Add("messaging_type", "RESPONSE")
Dim Med As New FacebookMediaObject()
Med.FileName = YourFile.FileName
Med.ContentType = YourFile.ContentType
Med.SetValue(YourFile.InputStream.ToBytes())
TmpParams.Add(YourFile.FileName, Med)
Client.Post("me/messages", TmpParams)
Using a Dictionary(of string,object), manually add the params for recipient,message,messaging_type and the file bytes.

Related

Passing a large file stream to MultipartFormDataContent with HttpClient

I'm experiencing a problem when trying to use MultipartFormDataContent with HttpClient with a stream of data.
Context
I'm trying to upload a large file to ASP.NET Core Web API. A client should send the file via POST request form-data to a front-end API, which in turn should forward the file to a back-end API.
Because the file can be large, I followed the Microsoft example, i.e. I don't want to use IFormFile type but instead read the Request.Body using MultipartReader. This is to avoid loading the entire file into memory on the server, or saving it in a temporary file on server's hard drive.
Problem
The back-end API controller action looks as follows (this is almost directly copied from the ASP.NET Core 5.0 sample app with just minor simplifications):
[HttpPost]
[DisableRequestSizeLimit]
public async Task<IActionResult> ReceiveLargeFile()
{
var request = HttpContext.Request;
if (!request.HasFormContentType
|| !MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaTypeHeader)
|| string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
{
return new UnsupportedMediaTypeResult();
}
var reader = new MultipartReader(mediaTypeHeader.Boundary.Value, request.Body);
/* This throws an IOException: Unexpected end of Stream, the content may have already been read by another component. */
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition,
out var contentDisposition);
if (hasContentDispositionHeader
&& contentDisposition!.DispositionType.Equals("form-data")
&& !string.IsNullOrEmpty(contentDisposition.FileName.Value))
{
/* Fake copy to nothing since it doesn't even get here */
await section.Body.CopyToAsync(Stream.Null);
return Ok();
}
section = await reader.ReadNextSectionAsync();
}
return BadRequest("No files data in the request.");
}
I managed to reduce the problem slightly by making an integration test using Microsoft.AspNetCore.Mvc.Testing NuGet package. The following test replaces the front-end API, so instead of reading Request.Body stream in a Web API, the test just tries to add StreamContent to MultipartFormDataContent and post it via HttpClient to the back-end API:
[Fact]
public async Task Client_posting_to_Api_returns_Ok()
{
/* Arrange */
await using var stream = new MemoryStream();
await using var writer = new StreamWriter(stream);
await writer.WriteLineAsync("FILE CONTENTS");
await writer.FlushAsync();
stream.Position = 0;
using var client = _factory.CreateDefaultClient();
/* Act */
using var response =
await client.PostAsync(
"Receive",
new MultipartFormDataContent
{
{
new StreamContent(stream),
"file",
"fileName"
}
});
/* Assert */
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
The back-end API controller then throws an IOException at await reader.ReadNextSectionAsync(), saying "Unexpected end of Stream, the content may have already been read by another component".
GitHub Repository (Complete Example)
I uploaded a complete example of the problem (including back-end API and the test) a GitHub repo.
Question
I must be doing something wrong. How can I forward a file received in a request with form-data content type in one service (front-end API) to another service (back-end API) without loading the entire file into memory or hard-drive in the front-end API, i.e. to just forward the stream of data to the back-end API?
Thanks in advance for any help.
I expected the same issue as you and it turned out that the MediaTypeHeaderValue.TryParse method parses the boundary value wrong as it wraps the string with '"' characters, because HttpClient sends the content type header like this:
multipart/form-data; boundary="blablabla"
So for me the solution was to add a Trim() method to boundary like this and pass that to the MultipartReader
var boundary = mediaTypeHeader.Boundary.Value.Trim('"');
var reader = new MultipartReader(boundary, request.Body);

How to replicate Postman POST request in C#

I'm fairly new to .NET's HTTPClient class, hence kindly excuse if I sounded noob. I'm tryin to replicate Postman's POST request in C# .Net and written following code. However I'm not getting any response but StatusCode: 404. Could someone assist understanding where I'm going wrong?
Also I'd like to understand, how do set Body in following code.
var httpClient = new HttpClient
{
BaseAddress = new Uri("https://testURL.com"),
Timeout = TimeSpan.FromMinutes(10)
};
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("audio/wav"));
httpClient.DefaultRequestHeaders.Add("Authorization", "Basic ldjfdljfdlfjdsjfdsl");
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("model", "Test"),
});
var result = httpClient.PostAsync("api/v1/recognize", content).Result;
Here is what I'm doing in Postman and it works:
"Params" in Postman refers to query parameters which are appended to the URL. You'll see that the URL in Postman contains the parameters you added in the "Params" tab:
However, it seems those are just dummy values you've entered so perhaps you don't need them? In any case, the way you add query parameters to the request for HttpClient is a little different as it needs to be added to the URL.
After that you also need to add the audio file as content to your request. At the moment you're setting the "Accept" header to "audio/wav" but you probably want to set the "Content-Type" header instead (or are you expecting a WAV file to be returned in the response too?).
As far as I can see this is what you're missing:
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromMinutes(10);
// Set request headers
httpClient.DefaultRequestHeaders.Add("Authorization", "Basic ldjfdljfdlfjdsjfdsl");
// Set query parameters
var uriBuilder = new UriBuilder("https://testURL.com/api/v1/recognize");
uriBuilder.Query = "model=Test";
// Build request body
// Read bytes from the file being uploaded
var fileBytes = File.ReadAllBytes(wavFilePath);
// Create request content with metadata/headers to tell the
// backend which type of data (media type) is being uploaded
var byteArrayContent = new ByteArrayContent(fileBytes);
byteArrayContent.Headers.ContentType = MediaTypeHeaderValue.Parse("audio/wav");
// Wrap/encode the content as "multipart/form-data"
// See example of how the output/request looks here:
// https://dotnetfiddle.net/qDMwFh
var requestContent = new MultipartFormDataContent
{
{byteArrayContent, "audio", "filename.wav"}
};
var response = await httpClient.PostAsync(uriBuilder.Uri, requestContent);
}
I haven't tested this of course against your application, but it should be something along the lines of this. It might be that the backend doesn't expect "multipart/form-data" and just needs the "audio/wav". I can't see the output headers in your Postman screenshots, but if so, you can use byteArrayContent directly instead of wrapping it in MultipartFormDataContent.
Note: Don't use httpClient.PostAsync(...).Result. If you want to use the asynchronous method, you should await it. Depending on your code, using Result might give you problems if you're not careful. And remember to dispose the HttpClient after use (easiest solution is to use a using statement). If you plan on reusing the HttpClient for more requests, you can avoid disposing it until you're done.

InvalidSignatureException with AWS Kinesis Video Stream C#

I am attempting to execute my own HTTP signed request since there is no SDK in C# for the PutMedia API for the AWS Kinesis Video Stream, but I am getting the following error message:
StatusCode: 403, ReasonPhrase: 'Forbidden'
x-amzn-ErrorType: InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/
Here is a gist of what my code looks like:
var streamName = "audio-stream-test";
var service = "kinesisvideo";
var endpoint = GetPutMediaEndpoint(streamName);
var host = GetHostFromEndpoint(endpoint);
var region = GetRegionFromEndpoint(endpoint);
var t = DateTime.UtcNow;
var canonical_uri = $"{endpoint}/putMedia";
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(canonical_uri));
httpRequestMessage.Headers.Add("connection", "keep-alive");
httpRequestMessage.Headers.Add("host", host);
httpRequestMessage.Headers.Add("Transfer-Encoding", "chunked");
httpRequestMessage.Headers.Add("user-agent", "AWS-SDK-KVS/2.0.2");
httpRequestMessage.Headers.Add("x-amzn-fragment-acknowledgment-required", "1");
httpRequestMessage.Headers.Add("x-amzn-fragment-timecode-type", "ABSOLUTE");
httpRequestMessage.Headers.Add("x-amzn-producer-start-timestamp", (t - DateTime.MinValue).TotalMilliseconds.ToString());
httpRequestMessage.Headers.Add("x-amzn-stream-name", streamName);
httpRequestMessage.Headers.Add("x-amz-security-token", sessionToken);
var byteArray = File.ReadAllBytes(filePath);
var content = new ByteArrayContent(byteArray);
httpRequestMessage.Content = content;
var httpClient = new HttpClient();
var aws4RequestSigner = new AWS4RequestSigner(accessKey, secretAccessKey);
var signedHttpRequestMessage = aws4RequestSigner.Sign(httpRequestMessage, service, region).Result;
var httpResponseMessage = httpClient.SendAsync(signedHttpRequestMessage);
Screenshot of Error
I am using the Aws4RequestSigner NuGet package to sign the request. Any ideas what I am doing wrong here? Has anyone tried to use the AWS Kinesis Video Stream with C#/.NET successfully?
Two potential issues with the pseudo-code.
If using session token then the request signing should include the session token as well not only access key/secret access key combination.
The body of the PutMedia is "endless" as it streams out as a realtime stream. As such, the data shouldn't be included in the signature calculation.
This is answer to your question "the actual "content" is not being added to the stream. I see the Put Connection from KVS but no data added".
After you get 200 by setting http headers properly for the signing with below code, you need to have your content set in signedHttpRequestMessage.
var httpResponseMessage = httpClient.SendAsync(signedHttpRequestMessage);

Microsoft cognitive service Recognize Text api

I call the microsoft recognize text api by passing the image that I had taken from my phone, there no error occur but every time the api will return me empty string as result doesn't matter what image I post . I try those image with the microsoft ocr api and it return me result, can anyone help ?
I call the microsoft recognize text api by passing the image that I had taken from my phone, there no error occur but every time the api will return me empty string as result doesn't matter what image I post.
In documentation of Recognize Text API, we can find:
The service has accepted the request and will start processing later.
It will return Accepted immediately and include an “Operation-Location” header. Client side should further query the operation status using the URL specified in this header.
I suspect that you directly get/extract content from the response after you made a request to recognize text, so the content would be string.Empty.
To get recognize text operation result, you need to make a further request using the URL specified in response header "Operation-Location".
In following test code, we can find the content is indeed empty.
Test Code:
var client = new HttpClient();
var queryString = HttpUtility.ParseQueryString(string.Empty);
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "{Subscription_Key_here}");
queryString["mode"] = "Printed";
var uri = "https://{region}.api.cognitive.microsoft.com/vision/v2.0/recognizeText?" + queryString;
HttpResponseMessage response;
var imagePath = #"D:\xxx\xxx\xxx\testcontent.PNG";
Stream imageStream = File.OpenRead(imagePath);
BinaryReader binaryReader = new BinaryReader(imageStream);
byte[] byteData = binaryReader.ReadBytes((int)imageStream.Length);
using (var content = new ByteArrayContent(byteData))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response = await client.PostAsync(uri, content);
if (response.IsSuccessStatusCode)
{
var contentString = await response.Content.ReadAsStringAsync();
var operation_location = response.Headers.GetValues("Operation-Location").FirstOrDefault();
Console.WriteLine($"Response content is empty({contentString == string.Empty}).\n\rYou should further query the operation status using the URL ({operation_location}) specified in response header.");
}
}
Test Result:
Note: Recognize Text API is currently in preview and is only available for English text. As you mentioned, OCR technology in Computer Vision can also help detect text content in an image, and OCR currently supports 25 languages, sometimes we can use OCR as an alternative solution.

Uploading .mp4 via HTTPS

I'm trying to upload an .mp4 file to Giphy.com's API. It says to send the file over as 'Binary' and I think I'm confused as what exactly they mean by that. Here's the docs if you scroll to the bottom at "Upload Endpoint". https://developers.giphy.com/docs/
Here's what I have right now.
I've tried multiple versions of this (using StringContent, MultipartFormDataContent, ByteArrayContent, HttpMessages... etc) and always get a '400 - Bad Request - No Source Url' (which the docs say isn't required if you upload you're own) which makes me believe the content isn't being recognized.
public async Task<HttpResponseMessage> UploadVideoAsync(StorageFile file)
{
using (var stream = await file.OpenStreamForReadAsync())
{
byte[] bytes = new byte[stream.Length];
await stream.ReadAsync(bytes, 0, (int)stream.Length);
Dictionary<string, string> dic = new Dictionary<string, string>
{
{ "file", Encoding.ASCII.GetString(bytes) },
{ "api_key", api_key }
};
MultipartFormDataContent multipartContent = new MultipartFormDataContent();
multipartContent.Add(new ByteArrayContent(bytes));
var response = await httpClient.PostAsync($"v1/gifs?api_key={api_key}", multipartContent);
var stringResponse = await response.Content.ReadAsStringAsync();
return response;
}
}
It seems that your code doesn't match {api_key} properly. You don't use the "dic" variable anywhere. You can try with v1/gifs?api_key=YOUR_API_KEY&file= instead. Where YOUR_API_KEY should be replaced by your API key obtained from giphy.
always get a '400 - Bad Request - No Source Url' (which the docs say isn't required if you upload you're own) which makes me believe the content isn't being recognized.
You need to apply a name for the ByteArrayContent. The document has shown that Request Parameters contains 'file: string (binary) required if no source_image_url supplied'.
The code should like the following:
MultipartFormDataContent multipartContent = new MultipartFormDataContent();
multipartContent.Add(new ByteArrayContent(bytes),"file");

Categories