cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Community Tip - Did you get an answer that solved your problem? Please mark it as an Accepted Solution so others with the same problem can find the answer easily. X

Upload file in a WindChill document - OData REST API

MarcoTeixeira
11-Garnet

Upload file in a WindChill document - OData REST API

I want to upload a file in WindChill. 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.

16 REPLIES 16

Hello Marco,

 

There is exists one interesting fact, that order of entities in form-data is important!

 

From java doc

 

    * <br>For regular upload, client needs to send the data in following format and order:
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="Master_URL"
    * <br>&lt;Master WindchillGW URL&gt;
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="CacheDescriptor_array"
    * <br>&lt;streamid&gt;:&lt;filename&gt;:&lt;contentid&gt;:&lt;filesize&gt;;&lt;streamid&gt;:&lt;filename&gt;:&lt;contentid&gt;:&lt;filesize&gt;;...
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="streamContentFromURL"
    * <br>&lt;file download URL&gt;
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="&lt;filename&gt;"; filename="&lt;filepath&gt;"
    * <br>&lt;actual content file&gt;
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="&lt;filename&gt;"; filename="&lt;filepath&gt;"
    * <br>&lt;actual content file&gt;
    * <br>-----------------------------boundary
    * <br>...
    * <br>
    * <br>For multi-chapter upload, client needs to send the data in following format:
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="Master_URL"
    * <br>&lt;Master WindchillGW URL&gt;
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="ChapteredCacheDescriptor_array"
    * <br>&lt;streamid&gt;:&lt;filename&gt;:&lt;contentid&gt;:&lt;filesize&gt;;&lt;streamid&gt;:&lt;filename&gt;:&lt;contentid&gt;:&lt;filesize&gt;;...
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="chaptered-upload-info"
    * <br>upload-chapters-count=&lt;Number of chapters to upload&gt;
    * <br>delegate=&lt;Delegate Name&gt; <nbsp/><nbsp/><nbsp/><nbsp/> (Optional)
    * <br>prev-content-data=&lt;Previous content data&gt; <nbsp/><nbsp/><nbsp/><nbsp/> (Required if you need to copy chapters)
    * <br>copyChapters=&lt;chaptername&gt;:&lt;chapter filesize&gt;:&lt;checksum&gt;
    * <br>copyChapters=&lt;chaptername&gt;:&lt;chapter filesize&gt;:&lt;checksum&gt;
    * <br>copyChapters=&lt;chaptername&gt;:&lt;chapter filesize&gt;:&lt;checksum&gt;
    * <br>...
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="Chapter";
    * <br>x-ptc-chapter-name:&lt;Chapter Name&gt;
    * <br>x-ptc-chapter-size:&lt;Chapter Size&gt;
    * <br>x-ptc-chapter-checksum:&lt;Check sum&gt; <nbsp/><nbsp/><nbsp/><nbsp/>(optional pass 0 if no value is available)
    * <br>x-ptc-stream-id:&lt;Stream Id&gt;
    * <br>&lt;actual content file&gt;
    * <br>-----------------------------boundary
    * <br>Content-Disposition: form-data; name="Chapter";
    * <br>x-ptc-chapter-name:&lt;Chapter Name&gt;
    * <br>x-ptc-chapter-size:&lt;Chapter Size&gt;
    * <br>x-ptc-chapter-checksum:&lt;Check sum&gt; <nbsp/><nbsp/><nbsp/><nbsp/>(optional pass 0 if no value is available)
    * <br>x-ptc-stream-id:&lt;Stream Id&gt;
    * <br>&lt;actual content file&gt;
    * <br>-----------------------------boundary

Hello,

 

Did you have finally solve your issue?

 

I try also to use those api rest to upload documents, but I struggle with PDF files.

 

It's working perfectly in case of simple ascii files, but I c'ant upload a pdf file: at the end, the file uploaded in windchill is corrupted.

I don't know how to create the body of the stage 2 for pdf or other binary files.

 

Thank you very much for your feedback

 

Thierry

fivig
10-Marble
(To:tchampier)

Could you post the MethodServer log exception?

tchampier
11-Garnet
(To:fivig)

Hello,

 

No, I'm not admin of the system so I've no acces on the Methodserver.

 

In fact, when I use the stage 2, I've no error message: at the end, I'm able to do stage 1, 2 and 3, the content of the WTDocument is created but in case of PDF, the file created in windchill is corrupted when I try to open it.

 

When I do that for a simple ascii file, it's working perfectly: file is created in Windchill and I can open it.

 

My issue is how created the body for the stage2 to be able to upload a pdf, and especially, the third part like this one: 

------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--

 

Thank you for your help

 

Hi @tchampier .

 

Yes, I solve it with the postman. You can configure all in the software and then he gives to you the code in a multiple program languages. With the postman you not need to construct all the body of the form, he simplifies that for you.

If you need help you can contact me. You can check my question and answer in the stackoverflow.

https://stackoverflow.com/questions/61360725/upload-file-in-server-with-a-multipart-form-data-c-sharp

 

Thank you for your feedback.

 

I'm using also Postman to try the request. I'll check your post on stackoverflow.

If I've some questions, I will come back to you.

 

Regards,

Thierry

Hello Marco,

 i'm facing the same problem with a docx document. I also I've tried to use postman but i receive a Bad Request error.

Have you solve using C# ?

 

Thank you

Hello,

 

Is it for the stage 2, when you need to upload the file on the server? 

 

Here an example of C# provided by Postman for the stage 2

 

var client = new RestClient("http://yourwindhillurl/Windchill/servlet/WindchillGW/wt.fv.uploadtocache.DoUploadToCache_Server/doUploadToChache_Replica?mk=wt.fv.uploadtocache.DoUploadToCache_Server&VaultId=2413044403&FolderId=12386665526&CheckSum=382389780&sT=1605624474&sign=ko2ejJm0QjxduNBL6Jhr75awZHQW0dLuTZ1sDeNs%2Fl5XAmjO23rN8mWrsbtPh0JtKz4UPn%2BoALSzLPgZ%2BZluMw%3D%3D&site=http%3A%2F%2Fyourwindhillurl%3A8585%2FWindchill%2Fservlet%2FWindchillGW&AUTH_CODE=RSA&isProxy=true&delegate=wt.fv.uploadtocache.DefaultRestFormGeneratorDelegate");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("CSRF_NONCE", "3R0H4e9CcTCVcH2Zsis+tY4bCQXbgKE3p7FZuhaJ7RFf0GhustVhXsosjdfg7K2HFEzChg2vQUup6Cs11d10QQenR0eqqU59kqoKM0n7Nxjzt3tJsrx3C2rETUA=");
request.AddHeader("Content-Type", "multipart/form-data");
request.AddHeader("Authorization", "Basic Q1QxMTk4MGflDDkkfcnVzMDkyMA==");
request.AddParameter("Master_URL", "http://yourwindhillurl/Windchill/servlet/WindchillGW");
request.AddParameter("CacheDescriptor_array", "93417607:97777282:93417607;");
request.AddFile("97777282", "/C:/test.pdf");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

 

In Postman, some screenshots to show how configuring the Header and  Body

tchampier_1-1641205332489.png

 

tchampier_0-1641205238436.png

 

If needed I can provide you more detail, don't hesitate to contact me.

Regards.

Thierry

Hello Thierry,

first of all thank you for the answer you provided me.

Analyzing your response  I think the problem is in the method used to calculate the file size express in byte stream (97777282 in your example).

This because the upload action is digged in a Web Application so I don't know the size of file; only his name.

 

Now I'm in vacation, I'll return on 2022/01/10; eventually i'll write you when i come back.

 

Thank you 

In the example, 97777282  is the ID provided by the stage 1. In fact, I don't put any information regarding the size file.

 

Have a good vacation time and no issue, you can contact me when you're back.

 

 

Hello everyone.

As @tchampier said the 97777282 is the ID. The file size is calculated when you call the stage 2. The file size is only needed when you run the stage 3.

@tchampier unfortunately I lost the video that I send to you. Do you still have the video? If you have can you please share with me and @gfontana?

Many thanks.

Hello Marco and Thierry,

I've found some time to test your solution.

 

On Method Server I've received this error:

2022-01-11 17:24:48,891 INFO [ajp-nio-127.0.0.1-8010-exec-3] wt.system.err - MPInputStream object body top boundary mismatch
2022-01-11 17:24:48,891 INFO [ajp-nio-127.0.0.1-8010-exec-3] wt.system.err - expecting --"34fecfe2-08f6-4c76-a884-ddbe2e89ae1f"
2022-01-11 17:24:48,891 INFO [ajp-nio-127.0.0.1-8010-exec-3] wt.system.err - found --34fecfe2-08f6-4c76-a884-ddbe2e89ae1f

 

I suppose there is something missing on my code

 

var byteArray = Encoding.ASCII.GetBytes(auth);
var client1 = new RestClient((string)htparm["replicaurl"]);
//client1.Timeout = -1;
var request = new RestRequest {
Method = Method.Post,
AlwaysMultipartFormData = true
};
request.AddHeader("CSRF_NONCE", (string)JoToken["NonceValue"]);
request.AddHeader("Content-Type", "multipart/form-data");
request.AddHeader("Authorization", "Basic " + Convert.ToBase64String(byteArray));
request.AddParameter("Master_URL", (string)htparm["masterurl"]);
request.AddParameter("CacheDescriptor_array", string.Format("{0}:{1}:{2}", htparm["streamid"], htparm["filename"], htparm["streamid"]));
request.AddFile(((Int64)htparm["filename"]).ToString(), (string)htparm["fullfilepath"], "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
RestResponse response = await client1.ExecuteAsync(request,CancellationToken.None);

where htparm is a hashtable containing all parameters such as Master URL, replica URL etc...

 

I'll try to find other time in the next day to further investigate. For now thank you for the support.

Giorgio

What you did was my first approach.

Never worked because the encoding of the file, I never discovered why.

The method that I use to solve this issue was to install Postman and make there the requests and after that extract the c# code from Postman.

 

The code for stage 2 is smiliar to the next one:


var client = new RestClient(Stage1.ReplicaUrl);
client.Authenticator = new HttpBasicAuthenticator("***", "***");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "multipart/form-data");
request.AddHeader("CSRF_NONCE", Token);
request.AddHeader("Accept", "*/*");
request.AddHeader("Accept-Encoding", "gzip, deflate, br");
request.AddHeader("Connection", "keep-alive");
request.AddParameter("Master_URL", Stage1.MasterUrl);
request.AddParameter("CacheDescriptor_array", Stage1.FileNames.First().ToString() + ":" + Stage1.FileNames.First().ToString() + ":" + Stage1.FileNames.First().ToString());
request.AddFile(Stage1.FileNames.First().ToString(), directoryOnDiskOfFile);
IRestResponse response = client.Execute(request);

Hello Marco and Thierry,

 I've changed the RestSharp version from 107 to 106.15.0 and all is starting to work.

Now I can upload the content and I have seen that the file is been loaded in the defualtuploadfolder. In addiction I can see the content using the browser; the only problem is that, when i try to download,  there is a mismatch from the name defined in uploadedfolder and the name used by servlet.

But it could be a problem in the vault management.

Thank you again for the support. 

 

Yes, I've saved it preciously 🙂

 

I will send it to @gfontana and you.

 

Regards,

Thierry

SM_11207762
4-Participant
(To:tchampier)

Hello @tchampier ,

 

Thank you for sharing the above details.

Could you please also share the Upload Stage 2 Action from Thingworx Service?

I am working on the Thingworx 9.5 Version.

Announcements


Top Tags