How to make multipart/mixed request containing nested HTTP requests - c#

I am trying to implement creating a multipart request where each part is a HTTP request
To see what I mean here's for example how this Google API works: https://cloud.google.com/dns/api/batch#example (I don't use that API, it's just an example)
I've seen examples using MultipartFormDataContent but I don't understand how to create each part"
--====1340674896===
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1111
POST /contacts/479038 HTTP/1.1
Content-Type: application/json
Accept: application/json
{ "SomeData" : 1 }
The content of each part is I think the serialized string of a request:
GET /contacts/479038 HTTP/1.1
Content-Type: application/json
Accept: application/json
{ "SomeData" : 1 }
So how do I create this? I guess I need to get the serialized version of a HttpRequestMessage?
Can I just serialize request.Content.ReadAsStringAsync())
In WebAPI I see there's this HttpContentMessage which takes a HttpRequestMessage and can serialize it:
var httpMessageContent = new HttpMessageContent(request);
var buffer = httpMessageContent.ReadAsByteArrayAsync().Result;
stream.Write(buffer, 0, buffer.Length);
But I don't have this class available in .NETStandard

Related

How to call Multipart message request in c#

I need to call an endpoint that requires the Request Header and Request Body with Content-type
and content-length and some other property as well which need to End with BOUNDARY.
Request Header
POST https://www.API-URL.com/ HTTP/1.1
Host: www.API-URL.com
Content-type: multipart/mixed; boundary=BOUNDARY
Content-length: 1034
Request Body
--BOUNDARY
Content-type: application/x-www-form-urlencoded
Content-length: 141
AppVersion=1.0&AcceptLicenseAgreement=Yes&ResponseType=application/x-x-pld&VersionNumber=xxx&UserId=xxxx&Password=xxxxx
--BOUNDARY
Content-type: application/x-x-binary
Content-length: 6
020020 // This the string Data which I need to post
--BOUNDARY--
How can I implement a call to this endpoint by using HttpClient?
Thanks in advance

Parse an content-type header from http request stream

I have the contents of an http request saved on disk.
It looks like this:
POST http://test.ca/ 1.1
Accept: application/xml
Content-Type: multipart/form-data; boundary="8942d141-7753-45ab-be3a-53021694e70c"
--8942d141-7753-45ab-be3a-53021694e70c
Content-Type: application/xml Content-Disposition: form-data; name=Bundle
...
--8942d141-7753-45ab-be3a-53021694e70c
Content-Type: image/jpg Content-Disposition: form-data; name="image 1 of 2"; filename=Attachment1.jpg; filename*=utf-8''Attachment1.jpg
...
--8942d141-7753-45ab-be3a-53021694e70c--
Normally, if you were processing this at the time of request, the HttpRequest object would be used to get the content type. If I have that request persisted to disk, are there any available classes I can use to handle parsing the ContentType for me?
Everything else can be parsed using the MultipartReader class, but I'm not aware of anything that will handle parsing the request header, given an instance of a stream. If I don't have to roll my own that would be great.

Uploading multipart form files with HttpClient

I am trying to create an attachment using the Support Bee API as documented here:
https://supportbee.com/api#create_attachment
I have written a service that uses an HttpClient to create and send the request using a filename.
If I test in in Postman, it succeeds. I am using form-data for the body and just selecting the file to upload from the UI:
It doesn't work when I try to upload it via my HttpClient Service:
public async Task<string> CreateAttachmentAsync(string fileName)
{
// "client" is HttpClient provided via D.I.
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(new StreamContent(new FileStream(fileName, FileMode.Open)), "files[]");
using (HttpResponseMessage response = await client.PostAsync(
"https://xxx.supportbee.com/attachments?auth_token=xxx",
content))
{
string responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
}
This results in a 500 Internal Server Error. Inspecting the MultipartFormDataContent object I can see that it's header values are automatically being set:
{
Content-Type: multipart/form-data; boundary="c9be3778-4de5-4460-9929-adcaa6bdda79"
Content-Length: 164
}
I have also tried reading the file to a byte array first and using ByteArrayContent instead of StreamContent to no avail. The response doesn't provide anything helpful, but since the request works in Postman I must have something wrong with my code, but I don't know what else to try.
Edit: I tested with Fiddler to compare the successful Postman request to my code. Here is the request with Postman:
POST
https://xxx.supportbee.com/attachments?auth_token=xxx
HTTP/1.1 User-Agent: PostmanRuntime/7.22.0 Accept: / Cache-Control:
no-cache Postman-Token: f84d22fa-b4b1-4bf5-b183-916a786c6385 Host:
xx.supportbee.com Content-Type: multipart/form-data;
boundary=--------------------------714700821471353664787346
Accept-Encoding: gzip, deflate, br Content-Length: 241 Connection:
close
----------------------------714700821471353664787346 Content-Disposition: form-data; name="files[]"; filename="sample.txt"
Content-Type: text/plain
This contains example text.
----------------------------714700821471353664787346--
And the failing request from my code:
POST
https://xxx.supportbee.com/attachments?auth_token=xxx
HTTP/1.1 Host: xxx.supportbee.com Accept: / Accept-Encoding:
gzip, deflate, br Connection: close Content-Type: multipart/form-data;
boundary="ea97cbc1-70ea-4cc4-9801-09f5feffc763" Content-Length: 206
--ea97cbc1-70ea-4cc4-9801-09f5feffc763 Content-Disposition: form-data; name="files[]"; filename=sample; filename*=utf-8''sample
This contains example text.
--ea97cbc1-70ea-4cc4-9801-09f5feffc763--
The difference I can see is that the individual part in Postman has its own Content-Type: text/plain header for the file, and mine doesn't. I'm unable to add this because if I try content.Headers.Add("Content-Type", "text/plain"); It fails with 'Cannot add value because header 'Content-Type' does not support multiple values.'
First, it's important to note that a 500 response is akin to an unhandled exception, i.e. it's a bug on their end and more or less impossible to know for sure what you did wrong. I would suggest reporting it to them and, although I'm not familiar with Support Bee, I would hope they have good support people who can help you troubleshoot. :)
But if you want to play the guessing game, I agree that subtle differences between your successful Postman call and your code are a good place to start. For that header, note that content is the MultipartFormDataContent. You actually want to set it on the StreamContent object.
Also, look at the request headers Postman is sending and see if Content-Disposition includes a filename. You might need to add that to your code too, if the API is expecting it.
Here's how to do both:
var fileContent = new StreamContent(File.OpenRead(path));
fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
content.Add(fileContent, "files[]", Path.GetFileName(path));
If that's not the problem, look at the "raw" version of the request body in Postman, as well as those 11 request headers, and see if you can spot anything else you might be missing.

Unit Testing a Web API call - How to use Raw Request Text for Test by turning it into a HttpRequestMessage?

I am writing a unit test for a Web API controller that accepts a file upload. I'd like to be able to take a Raw Request message stored as a string in a "Test Resources" file and turn it into an HttpRequestMessage. How can I do this?
For example:
POST http://local/api/File/26 HTTP/1.1
Accept: */*
Authorization: f1697cb7-7dd4-49c7-87fd-3f09bc3f3d7a
ServiceId: 0
Partition: 9000
Content-Type: multipart/form-data; boundary="Upload----05/06/2014 14:55:27"
Host: local
Content-Length: 179
Expect: 100-continue
--Upload----05/06/2014 14:55:27
Content-Disposition: attachment; filename=MyTestFile.txt
Content-Type: application/x-object
Hello World!!
--Upload----05/06/2014 14:55:27--
This Raw request would be stored in the resource file, and I would end up with it parsed and processed into a HttpRequestMessage for my test.
Short of doing this manually building out the Content structure is there an easy way to do this?
I am not aware of any easy way to parse this raw text to HttpRequestMessage...but have you considered building this multipart form data request using MutlipartFormDataContent instead? following is an example
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:9095");
HttpRequestMessage request = new HttpRequestMessage();
MultipartFormDataContent mfdc = new MultipartFormDataContent();
mfdc.Add(new StringContent("Hell, World!"), "greeting");
mfdc.Add(new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("This is from a file"))), name: "Data", fileName: "File1.txt");
HttpResponseMessage response = client.PostAsync("/api/File", mfdc).Result;

Send multiple of items to WCF Dataservice at once?

I'm using WCF Data Service as a way to allow other webservice in the project to connect to the database. My problem is that I our project has a crawler that add tens of items to the database every hour.
Using AddToItems method (which is auto generated by ADO.NET) leads to timeout exception or at least it makes the crawler in need to wait for a lot of time taking into consideration that Addto method handles each independently.
*Notes :
1- I've added an interceptor on adding to items to perform some actions when a new item is added.
2- WCF Data services Service Operations doesn't allow taking parameters of a user defined data type , that prevented me from creating a service operation that takes a list of items as a parameter to be able to handle multiple items at each time and at the same way to allow the client to handle it asynchronously.
When I tried to serialize this list so it can be treated as a string , an exception has occurred because of the length limit of the url even when POST is used instead of Get.
Update : Saveing Changes via BeginSaveChanged and EndSaveChanged solved the problem to some extent but I'm still looking for a better solution
Maybe the OData feature Batch can address your requirement:
var client = new Container(serviceUrl);
client.Format.UseJson();
DefaultBatchCustomer customer0ToAdd = new DefaultBatchCustomer { Id = 10, Name = "Customer 10" };
DefaultBatchCustomer customer0ToAdd = new DefaultBatchCustomer { Id = 11, Name = "Customer 11" };
client.AddToDefaultBatchCustomer(customer0ToAdd);
client.AddToDefaultBatchCustomer(customer1ToAdd);
var response = await client.SaveChangesAsync(SaveChangesOptions.BatchWithSingleChangeset);
and the request looks like:
POST http://jinfutanwebapi1:9123/DefaultBatch/$batch HTTP/1.1
OData-Version: 4.0;NetFx
OData-MaxVersion: 4.0;NetFx
Content-Type: multipart/mixed; boundary=batch_8ce61768-e8bb-4117-954b-9bc43e05baef
Accept: multipart/mixed
Accept-Charset: UTF-8
User-Agent: Microsoft ADO.NET Data Services
Host: jinfutanwebapi1:9123
Content-Length: 1772
Expect: 100-continue
--batch_8ce61768-e8bb-4117-954b-9bc43e05baef
Content-Type: multipart/mixed; boundary=changeset_b36bec94-fc3b-4d89-99cc-0610fcec8148
--changeset_b36bec94-fc3b-4d89-99cc-0610fcec8148
Content-Type: application/http
Content-Transfer-Encoding: binary
POST http://jinfutanwebapi1:9123/DefaultBatch/DefaultBatchCustomer HTTP/1.1
Content-ID: 13
OData-Version: 4.0;NetFx
OData-MaxVersion: 4.0;NetFx
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
User-Agent: Microsoft ADO.NET Data Services
{"#odata.type":"#WebStack.QA.Test.OData.Batch.Tests.DataServicesClient.DefaultBatchCustomer","Id":10,"Name":"Customer 10"}
--changeset_b36bec94-fc3b-4d89-99cc-0610fcec8148
Content-Type: application/http
Content-Transfer-Encoding: binary
POST http://jinfutanwebapi1:9123/DefaultBatch/DefaultBatchCustomer HTTP/1.1
Content-ID: 14
OData-Version: 4.0;NetFx
OData-MaxVersion: 4.0;NetFx
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
User-Agent: Microsoft ADO.NET Data Services
{"#odata.type":"#WebStack.QA.Test.OData.Batch.Tests.DataServicesClient.DefaultBatchCustomer","Id":11,"Name":"Customer 11"}
--changeset_b36bec94-fc3b-4d89-99cc-0610fcec8148--
--batch_8ce61768-e8bb-4117-954b-9bc43e05baef--

Categories

Resources