Jump to content
Yaron

Handling MultipartFormData

Recommended Posts

I am trying to upload JSON data and an image as a MultipartFormData and return a JSON string from mars using the following code:
 

var

  shareData  : TMultipartFormData;

  jsonStr : String;

begin

  shareData := TMultipartFormData.Create;
  shareData.AddField('json','{"empty":"empty"}');
  shareData.AddFile('image',FileName);

  jsonStr  := NetHTTPRequest.Post(urlAPIBase+urlAPIShareImage,shareData).ContentAsString(TEncoding.UTF8);

end;

 

How do I setup mars to give me access to the json field and image fields?

 

I looked at the ContentTypes demo, but I wasn't sure if any of the examples actually covered MultipartFormData.

Share this post


Link to post

Hi, there was no demo showcasing how to handle multipart/form-data on the server side with MARS. But, since a couple of minutes, there is! 🙂

Have a look here: https://github.com/andrea-magni/MARS/tree/master/Demos/MultipartFormData

 

Build and run the demo, then use a browser to navigate http://localhost:8080/rest/default/helloworld/1 to have a simple HTML page with a multipart/form-data form.

The last part of the URL can be 1, 2, 3 or 4.

On the server side, you will notice four methods marked with the POST attribute, to handle the 4 requests. It is actually the same http request served through 4 different available ways in MARS (see here: https://github.com/andrea-magni/MARS/blob/master/Demos/MultipartFormData/Server.Resources.pas😞

 

1) ask the library to inject a dynamic array of TFormParam:

[FormParams] AParams: TArray<TFormParam>

Implementation of the REST method loops on that array to provide a result value;

 

2) target specific params (by name) using the following syntax:
 

[FormParam('json')] AJSON: TFormParam; 
[FormParam('image')] AImage: TFormParam;

FormParam is defined in MARS.Core.Utils and can represent both simple parameters and files;

 

3) push a bit more and ask for automatic deserialization of the first parameter into a TJSONObject. The methods has these arguments defined: 

[FormParam('json'), Consumes(TMediaType.Application_JSON)] AJSON: TJSONObject; 
[FormParam('image')] AImage: TFormParam;

Note the type of the first argument is no more TFormParam but TJSONObject

 

4) last version takes advantage of JSON to record serialization (built-in with MARS):

[FormParam('json'), Consumes(TMediaType.Application_JSON)] ARecord: TPerson;
[FormParam('image')] AImage: TFormParam;

 

All these four variations have a TMyResult record as return value. This record gets automatically serialized to JSON by the library.

You should be able to build clients in any language (I tested with Postman and the HTML page, using Chrome).

 

Let me know if everything is clear and working.

 

Sincerely,

Andrea

  • Like 1

Share this post


Link to post

Hi Andrea,

 

I want to implement this in my server, so I started with your demo above and tried to use Postman to test the API.    When I run Postman against your [..]/helloworld/2 endpoint I get the following back:

{
    "JSONData": "{\"name\": \"Stuart\"}",
    "PersonName": "Stuart",
    "FileName": "",
    "FileSize": 0
}

This is my PostMan Setup:  https://imgur.com/td0fDJ4  (Sorry, can't find anyway to export the Postman config for that request).

 

The json data is picked up okay, but not the file data.  I'm sending an Excel file in this case, but I've tried pure binary files like JPGs too.

 

Can you see what I'm doing wrong with Postman? 

 

 

Thanks

Edited by Stuart Clennett
(Added more info about what was being sent & updated Image URL)

Share this post


Link to post

Hi @Stuart Clennett everything seems fine in your screenshot (as long as you actually loaded a file as param's value but I take this for granted).

It turned out to be a bug in MARS I introduced with commit https://github.com/andrea-magni/MARS/commit/a0bcbce5c43dbb45648d89e6bc3a44b74e7996bd 

Introducing the RequiredAttribute to enforce FormParam validation I broke the reading mechanism of data from multipart/form-data files.

 

I fixed this in the develop branch with commit https://github.com/andrea-magni/MARS/commit/bf84a00b0f5e45d8760ab9b0a24d7c6ee018aca4

I also backported a similar fix to master branch with commit https://github.com/andrea-magni/MARS/commit/63a4bbb36d4f6acb18106086f889b76ac69f2882

 

Please update your working copy (either develop or master) and confirm this solves the problem.

 

Thanks a lot for pointing this out and sorry for the inconvenience.

Sincerely,

Andrea

Share this post


Link to post

Hi Andrea,

 

Thanks very much.  Confirmed working with Postman and the delphi demo client.  

 

Still can't get my HTML to work though - it just uploads the file name not the file contents.  Looking into this now.

 

*Edit* My bad forgot the enctype="multipart/form-data" attribute from the form tag (which is ironic given that I was testing multipart/form-data, ha ha)

 

Kind regards,

 

Stuart

Edited by Stuart Clennett
added result of testing with html
  • Like 1

Share this post


Link to post

Hello to all!
I am trying to use this demo.
I get a positive response from the server, but I can not find the received file.

Please tell me where the received file is saved?

Share this post


Link to post

@alejandro

 

The demo does not save the file at all.  You are required to implement that.  However it is quite simple.

 

For example, in the `StoreDataAndFile` method in `Server.Resources.pas` you could use a memory stream to access the Bytes and save to a file

 

    if SameText(LParam.FieldName, 'image') and LParam.IsFile then
    begin
      Result.FileSize := Length(LParam.AsFile.Bytes);
      Result.FileName := LParam.AsFile.FileName;

      // This will save the file
      lStream := TMemoryStream.Create;
      try
        lStream.Write(LParam.AsFile.Bytes, Length(LParam.AsFile.Bytes));
        lStream.SaveToFile(TPath.Combine(C_RootPath, TPath.GetFileName(LParam.AsFile.FileName)));
      finally
        lStream.free;
      end;

    end

In this instance,  `C_RootPath` is just a folder on the server where uploaded files should be saved, but you could make this dynamic based on user info in the Token for example. 

 

Hope this helps

Edited by Stuart Clennett
Changed formatting of code from HTML to Pascal
  • Like 1
  • Thanks 2

Share this post


Link to post

Hi Andrea and fellows,

 

we personally talked about your framestand on EKON.

Now we use MARS in our app very successfully. Its great!
But heres the problem... maybe i just dont get it. But i fail with building GET for the multipart/form-data!

I send a image from client to server, like in your demo. But the opposite way didnt work.

 

After reviewing the source i recognized that theres no override for AfterGet in the MARS.Client.Resource.FormData.pas.
Therefore FResponse wont be upgraded after you did .GET.

When i add it like AfterPut and AfterPost, it works.

 

Is there a reason for the missing AfterGet?

 

Best regards

Edited by Andre G

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×