Upload file in server with a multipart/form-data - C# - c#

I want to upload a file in WindChill (is a PLM from PTC). They give to us an REST API with the services to do this. They split an upload of file in 3 stages.
Stage 1 - We call a service where we give the number of files to upload. In this case only one.
Stage 2 - A multipart/formdata where we give the file to upload.
Stage 3 - The last stage where we give the file name, the file size etc...
I think my problem is on stage 2.
All the stages run successfully but when i try to open the uploaded file, in this case a pdf, the file is blank, but with the same number of pages of the original one. I compare the content of the uploaded file with the original one and the content inside is the same with a big difference. The original is with an ANSI encoding while the uploaded one is with the UTF-8 encoding. So, I think my problem is on the stage 2.
I'm with some doubts on this stage. In C# I get the bytes[] of file, but in the end I need to pass this bytes to a string to send in a multipart form. What is the encoding that i should use to get string? I tested with default, UTF-8, UNICODE, ASCII encoding but nothing.
Here is the example of the Post request body. In a C# I use the HTTPWebRequest to make a request.
------boundary
Content-Disposition: form-data; name="Master_URL"
https://MyUrl/Windchill/servlet/WindchillGW
------boundary
Content-Disposition: form-data; name="CacheDescriptor_array"
844032:844032:844032;
------boundary
Content-Disposition: form-data; name="844032"; filename="newDoc.pdf"
Content-Type: application/pdf
%PDF-1.7 //// The content of the file starts here
%ยตยตยตยต
1 0 obj
........
------boundary--
Before this approach I tried to convert the bytes[] ToBase64String and send an body like this:
------boundary
Content-Disposition: form-data; name="Master_URL"
https://MyUrl/Windchill/servlet/WindchillGW
------boundary
Content-Disposition: form-data; name="CacheDescriptor_array"
844033:844033:844033;
------boundary
Content-Disposition: form-data; name="844033"; filename="newDoc.pdf"
Content-Type: application/pdf
Content-Transfer-Encoding: base64
JVBERi0xLjcNCiW1tbW1DQox ........ //// The content of the file starts here
------boundary--
In this case, when I try to open the file i get the error "Failed to load PDF document". The file is corrupt.
I think the problem is on the stage 2, but I will share the body that i send in last stage for your understanding.
{"ContentInfo":[{"StreamId":844034,"EncodedInfo": "844034%3A40384%3A9276564%3A844034","FileName": "newDoc.pdf","PrimaryContent": true,"MimeType" : "application/pdf","FileSize" : 40384}]}
The StreamId and the EncodedInfo are returns of the stage 2 that I need to provide in the stage 3.
Anyone can see what I'm doing wrong? Anyone have some tips to help me to solve this issue?
Many thanks.

I have a big tip for solve issues like that.
Use postman. Make all the job in postman. After your job is done, you can generate the code for multiple languages with postman. Many thanks.

Related

Sending/Uploading Files through APIs - .Net Core

I would like to send a file through an API. The server will receive the file and save it on the server drive.
The question is that I have two Options to do this:
Read the file as a string and send the whole string in the request Body
Use multi-part
Could someone help in the pros and cons in using the two options.
It's not related to pros and cons, So basically to understand what is multipart means
Multipart requests combine one or more sets of data into a single
body, separated by boundaries. You typically use these requests for
file uploads and for transferring data of several types in a single
request (for example, a file along with a JSON object).
This mean that multipart contains different section of info that separated by boundary(random number).
For example:
POST /upload HTTP/1.1
Content-Length: 428
Content-Type: multipart/form-data; boundary=abcde12345
--abcde12345
Content-Disposition: form-data; name="id"
Content-Type: text/plain
{...Additional plain content goes here...}
--abcde12345
Content-Disposition: form-data; name="address"
Content-Type: application/json
{
"street": "3, Garden St",
"city": "Hillsbery, UT"
}
--abcde12345
Content-Disposition: form-data; name="profileImage "; filename="image1.png"
Content-Type: application/octet-stream
{...file content...}
--abcde12345--
In this example you can see different content sent in one request separated by boundary [boundary=abcde12345]. (Plain text, Json Object and File Content)
The file content section here is used for sending file data in application/octet-stream (string in the binary or base64 format), so basically you are sending the file data in form of string :) as you referred to the first point, but you can include some additional info, may be the new file name to be saved , the user who uploaded this file or whatever you want.
Hope you get the point.
Ref:
https://swagger.io/docs/specification/describing-request-body/file-upload/
https://swagger.io/docs/specification/describing-request-body/multipart-requests/
The only difference on the protocol level is that multipart/form-data requests must adhere to RFC 2388 while a custom typed request body can be arbitrary.
Using Base64 string has an advantage for uploading very tiny individual images. It's easy to handle and avoids dependency on Http.IFormFile. On the contrary, base64 encoded files are larger than the original and you need decode it on the server side.

How to call methods after HttpResponse?

I am a bit puzzled at the moment. I have a web application that manipulates a file and then returns the file to a user's browser for download when it's done.
The download part is going well, as I'm using Response.AddHeader and Reponse.BinaryWrite to push the file back to the browser but I am unable to call any further methods after using Response methods.
I suppose I have not worked with HttpReponse enough to know the trick to this. Perhaps I would be better off using another class or generic handler to handle the download?
My code goes something like...
// Methods to be called first
Response.Clear();
Response.ClearContent();
Response.ClearHeaders();
Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}.pdf", "New_Merged_PDF_" + DateTime.Now.ToString("MMMM-dd-yyyy")));
Response.ContentType = "application/pdf";
Response.BinaryWrite(output.ToArray());
Response.End();
// Methods to be called last (these wont work)
Probably something simple that I'm overlooking but I'm still trying to figure it out.
To add a little color to Servy's explaination; there is an order of operations within the HTTP protocol specification. One of them is that the Headers need to be sent to the client before the Body. This allows the receiver of the response to properly deal with the Body that is sent based on any Headers.
The presence of a message-body in a request is signaled by the
inclusion of a Content-Length or Transfer-Encoding header field in
the request's message-headers.
IETF RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1
One of the few (if the only, I'm not sure) exception is the Content-Type: multipart/mixed;
IETF RFC 1867 - Form-based File Upload in HTML (although Obsolete, examples are still relevant)
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--AaB03x--

Can't Programatically Upload csv File with WebClient .Net 4.0

I am attempting to upload a csv file but I can't seem to get it to work programatically. If I use Postman in Chrome to send the file it works and here is what it sends (Fiddler output):
------WebKitFormBoundary2YsMyLR3QAPruTy4
Content-Disposition: form-data; name="Content-Type"; filename="613022.csv"
Content-Type: application/vnd.ms-excel
// File Content here
------WebKitFormBoundary2YsMyLR3QAPruTy4--
However, using this code:
WebClient wc = new WebClient();
wc.Credentials = new NetworkCredential(user, pw);
wc.Headers[HttpRequestHeader.Cookie] = "OBBasicAuth=fromDialog";
wc.Headers[HttpRequestHeader.ContentType] = "application/vnd.ms-excel";
wc.UploadFile(baseURL + service + apiVersion + resource, "post", file);
Results in (Fiddler output):
-----------------------8d101dbe85fe96c
Content-Disposition: form-data; name="file"; filename="613022.csv"
Content-Type: application/vnd.ms-excel
// File Content here
-----------------------8d101dbe85fe96c--
Which does not work and the server returns a 503 error. The only difference I see is in the Content-Disposition name. How can I set this or is there a better way to accomplish this?
Perhaps you need "upload" instead of "post" in the method parameters - just guessing.
I believe the problem with your original approach is that WebClient.UploadFile generates the multipart/form-data request in a way that is unexpected by the server, hence the 5xx error code.
After looking around for a bit, I think the answer to this question should give you a starting point to tweak the request according to your needs.

Multipart request loses information on larger file uploads from IE 7/8

I'm not sure if it happens in other browsers, because in other browsers, we're able to check the file size on the client side after the file is selected. We don't allow uploads larger than 2MB, so we will block the user from the UI side if they select a file too large.
But IE 7/8 apparently doesn't allow us to do this. So we'd like to detect this server side and send back the appropriate message. However, exceptions are thrown when we see that parts of the request are missing.
I can see the properties in my Fiddler request, so I know they're being sent:
-----------------------------7db16b332033c
Content-Disposition: form-data; name="folderId"
CA15R1bH08ZxJ`DPL0mstwGjHMlrjKobu9VHBoPASniX8`UXgM8dnazjICvwfUj2qUKk14rh|NlB|uaUNkiVjPAqkX`kxWRl
-----------------------------7db16b332033c
Content-Disposition: form-data; name="docGuid"
-----------------------------7db16b332033c
Content-Disposition: form-data; name="fromSharedTree"
false
However, the properties are not available when I execute the following code, so exceptions are thrown.
string categoryId = request["folderId"];
string docGuid = request["docGuid"];
bool isUpdate = !string.IsNullOrWhiteSpace(docGuid);
bool isShared = bool.Parse(request["fromSharedTree"]);
Where did the values go? How do I get them back!?
Edit
Any attempt to access Request.InputStream results in an exception in this scenario. That would seem to be related. Who's imposing the size limit here? Is it .NET? IIS?
On the server side here's how you handle uploaded file sizes:
foreach (HttpPostedFileBase file in Request.Files)
{
if (file.ContentLength > 2097152)
{
// ... this file is larger than 2 MB
}
}
Also make sure that you have set a sufficiently large maxRequestLength in your web.config (<httpRuntime executionTimeout="240" maxRequestLength="20480" />) or the controller action might not be hit at all if the file is bigger than the allowed quota.
You might also find the following article useful for uploading files in ASP.NET MVC.

Error in reading image from NetworkStream

I have recieved an image with some text while reading from a NetworkStream. The stream includes something like this:
HTTP/1.0 200 OK
Expires: -1
Cache-Control: no-cache
Content-length: 29160
Content-type: image/jpeg
...followed by the image.
How can I read just the image from the NetworkStream?
You would have to parse the HTTP header first, to know where to stop discarding data. Alternatively, save the whole thing and then examine it afterwards, which may be simpler. Basically you'd be looking for two ASCII carriage-return/line-feed ("\r\n") pairs in a row.
However, there's a much better alternative: use an HTTP library. Parsing this yourself is like using text manipulation to handle XML; you're better off working at a higher level of abstraction with code which has been well tested for that abstraction.

Categories