Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multipart/form-data request content type is not handled properly #220

Closed
Tracked by #1 ...
baywet opened this issue Jun 7, 2021 · 22 comments · Fixed by #2997
Closed
Tracked by #1 ...

multipart/form-data request content type is not handled properly #220

baywet opened this issue Jun 7, 2021 · 22 comments · Fixed by #2997
Assignees
Labels
enhancement New feature or request generator Issues or improvements relater to generation capabilities. WIP
Milestone

Comments

@baywet
Copy link
Member

baywet commented Jun 7, 2021

The content serialization won't work as we don't have a serializer for it. Adding a serializer should be trivial using the natively available libraries on the platform to build the body so encoding is handled for us.

More reading

Originally reported
AB#9788

@baywet baywet added the enhancement New feature or request label Jun 7, 2021
@baywet baywet added this to the Beta milestone Jun 7, 2021
@baywet baywet self-assigned this Jun 7, 2021
baywet added a commit that referenced this issue Jun 7, 2021
@baywet baywet added the generator Issues or improvements relater to generation capabilities. label Jul 21, 2021
@baywet
Copy link
Member Author

baywet commented Sep 16, 2021

@baywet
Copy link
Member Author

baywet commented Mar 18, 2022

when implemented, we should clear the appsettings.json to be able to ship self-contained executable

@darrelmiller
Copy link
Member

@darrelmiller
Copy link
Member

OpenAPI specification example of multipart forms.
https://spec.openapis.org/oas/v3.1.0#encoding-object-example

In OpenAPI 3.0, JSON Schema isn't rich enough to describe multipart forms, so you may need a combination of schema object and encoding object. However, the encoding object is rarely used, so I suggest we wait until we add support for OpenAPI 3.1 as then all the information that is needed is in the JSON Schema. This will avoid us having to add support for the encoding object.

We should still be able to support multipart/form-data media types with scalar parts without support for the encoding object.

@baywet
Copy link
Member Author

baywet commented Dec 2, 2022

This one is a bit more complex to handle since multipart are effectively a composition of other serialization formats. This new serializer/deserializer would effectively need to be a composition of others (configurable, should be easily done).
The breaking change comes where models need to carry the information somehow that property A is serialized as JSON and property B as YAML.
We don't have a place holder to carry that information today and it'd probably represent a breaking change.

It'd also be a break of our promise that models being generated are serialization format agnostic.

@darrelmiller to think about that further and come up with guidance.

@baywet
Copy link
Member Author

baywet commented Dec 13, 2022

Another interesting point that @ddyett brought up is the fact that there's probably not even a way to describe the sub parts mime type in open API today. At the end of the day we're most likely blocked by that dependency besides giving customers access to the stream directly and some task design to compose the request body like we do for batches today.

@baywet
Copy link
Member Author

baywet commented Jan 20, 2023

Meeting notes: we don't need an additional task, we could code-gen them but use an aggregator of parts instead of a stream.

Example of usage:

var multipartFormBody = new MultipartFormBody(); // this would come from abstractions
multipartFormBody.AddStructuredPart("data", "application/json", oneNotPageParsable);
multipartFormBody.AddRawPart("picture", "image/jpeg", stream, optionalHeaders);
await client.Me.Notes.PostAsync(multipartFormBody);
//we'd new a new serialization/parse node library with a reference to the registry
var multipartFormBody = await client.Me.Notes.GetAsync(); // pass the parse node registry so it can be used when trying to get parts
var pageInfo = multipartFormBody.GetStructuredPart<PageInfo>("data");
var headers = multipartFormBody.GetPartHeaders("data")
var imageStream = multipartFormBody.GetRawPart("picture");

@baywet
Copy link
Member Author

baywet commented Jul 21, 2023

@andrueastman @rkodev I've finally been able to make progress on that...
I still need to work on the generation part and on the Go/Java implementations but you can have a look at
microsoft/kiota-dotnet#106 and https://github.com/microsoft/kiota-serialization-multipart-dotnet to start giving me feedback.

in terms of usage here is what it looks like

var mpBody = new MultipartBody();
mpBody.AddOrReplacePart("Presentation", "text/html", htmlPayload);
mpBody.AddOrReplacePart("imageBlock1", "image/png", imgStream);

await apiClient.Users["id"].OneNote.Pages.PostAsync(mpBody);

I've considered for a while trying to put all the parts as parameters of PostAsync, but that'd mean that adding a part in the schema would be binary breaking. Not ideal. Plus there are cases where we don't know all the parts in advance (e.g. one note, the number of images in any given page is variable).
That kind of requires the user to know the part names (not an issue for OneNote since all the part names except for presentation depend on what's in presentation) and their mime types.

Andrew: would you mind sharing what API surface v4 was offering until now please?

@andrueastman
Copy link
Member

@baywet
In V4, the API simply used MultipartFormDataContent which is a derived instance of HttpContent. This would simply be set to the HttpRequestMessage instance by passing it as a parameter. This was however not generated by Typewriter as the metadata incorrectly sets the content as a json but added as an overload/extension to the generated sdk.

var multipartContent = new MultipartFormDataContent();
var htmlString =
  "<!DOCTYPE html>\n<html>\n  <head>\n    <title>A page with <i>rendered</i> images and an <b>attached</b> file</title>\n    <meta name=\"created\" content=\"2015-07-22T09:00:00-08:00\" />\n  </head>\n  <body>\n    <p>Here's an image from an online source:</p>\n    <img src=\"https://...\" alt=\"an image on the page\" width=\"500\" />\n    <p>Here's an image uploaded as binary data:</p>\n    <img src=\"name:imageBlock1\" alt=\"an image on the page\" width=\"300\" />\n    <p>Here's a file attachment:</p>\n    <object data-attachment=\"FileName.pdf\" data=\"name:fileBlock1\" type=\"application/pdf\" />\n  </body>\n</html>";
var presentation = new StringContent(htmlString, Encoding.UTF8, "text/html");
multipartContent.Add(presentation,"Presentation");//needs a name

await graphClient.Users["id"].OneNote.Pages.Request().AddAsync(multipartContent);

@baywet
Copy link
Member Author

baywet commented Jul 25, 2023

Thanks for sharing the information. I've also created an issue in the conversion library so we can emit the right metadata.
Also after internal discussions, we'll only implement serialization for now, not deserialization.

@baywet
Copy link
Member Author

baywet commented Aug 1, 2023

onenote example

'/users/{user-id}/onenote/pages':
    post:
      parameters:
        - name: user-id
          in: path
          description: The unique identifier of user
          required: true
          style: simple
          schema:
            type: string
          x-ms-docs-key-type: user
      description: Create a new page in the specified section.
      tags:
        - pages.create
      operationId: create_page
      requestBody:
        content:
          'multipart/form-data':
            schema:
              properties:
                Presentation:
                  type: string
            encoding:
              Presentation:
                contentType: text/html
      responses:
        '204':
          description: No Content

@billybraga
Copy link

Where can we track the progress for this in the dotnet client generator?

@baywet
Copy link
Member Author

baywet commented Aug 12, 2024

@billybraga this is already implemented?

@billybraga
Copy link

Really? I have something like this:

...
  "requestBody": {
    "content": {
      "multipart/form-data": {
        "required": [
          "file",
          "metadata"
        ],
        "type": "object",
        "properties": {
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true
          },
          "file": {
            "type": "string",
            "format": "binary"
          }
        },
        "additionalProperties": false
      }
    },
    "required": true
  }
...

And I get something like this:

    public partial class CreateFeatureDocumentRequest : IParsable
    {
        /// <summary>The file property</summary>
        public string File { get; set; }

@baywet
Copy link
Member Author

baywet commented Aug 12, 2024

Have you tried using the MultipartBody ?
This is what should be used as a request body argument for the operation.

@billybraga
Copy link

I'm not sure how I would, the generated client method looks like this:

public async Task PostAsync(CreateFeatureDocumentRequest body //...

Also, I did not find any documentation for using MultipartBody

@baywet
Copy link
Member Author

baywet commented Aug 12, 2024

Can you start a new issue so we can continue the conversation please?
For the lack of documentation, can you create an additional issue here please? https://github.com/MicrosoftDocs/openapi-docs/issues

@billybraga
Copy link

Sorry for disturbing again, but I'll post my solution here in case someone ends up in the same situation:

I needed to add "encoding" in my open api spec:

...
  "requestBody": {
    "content": {
      "multipart/form-data": {
        "required": [
          "file",
          "metadata"
        ],
        "type": "object",
        "properties": {
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true
          },
          "file": {
            "type": "string",
            "format": "binary"
          }
        },
        "additionalProperties": false,
        // this is what was missing
        "encoding": {
          "file": {
            "style": "form"
          }
        }
      }
    },
    "required": true
  }
...

Without this, kiota doesn't generate MultipartBody.

https://github.com/microsoft/kiota/blob/main/src/Kiota.Builder/KiotaBuilder.cs#L1483

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request generator Issues or improvements relater to generation capabilities. WIP
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

5 participants