Skip to content

[C#] aspdotnetcore (C# server) generation does not support byte/binary body #1327

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

Open
grmcdorman opened this issue Oct 26, 2018 · 5 comments

Comments

@grmcdorman
Copy link

Description

When an OpenAPI spec describes the request with a body parameter of either:
schema: {
"type": "string",
"format": "binary"
}
or
schema: {
"type": "string",
"format": "byte"
}
the generated controller, correctly, has a [FromBody] System.IO.Stream or [FromBody] byte[] parameter. However, Swashbuckle out of the box does not support either type of parameter, resulting in 415 responses to any request sent, regardless of content type.

openapi-generator version

Discovered using the current git main branch.

OpenAPI declaration file content or url

'/data': {
'put': {
'parameters': [
{
"name": "data",
"in": "body",
"description": "Binary body data",
"required": true,
"schema": {
"type": "string",
"format": "byte"
}
}
}

Command line used for generation

java -jar <openapi-generator-cli.jar> generate -i <openapi.json> -g aspnetcore -o -c

Steps to reproduce

Specify an operation with the body as described above.

Related issues/PRs
Suggest a fix/enhancement

The scheme described at https://weblog.west-wind.com/posts/2017/Sep/14/Accepting-Raw-Request-Body-Content-in-ASPNET-Core-API-Controllers#Binary-Data provides a solution for forwarding the body in the appropriate format.

In addition, it is necessary to tell Swashbuckle that the content type is to be 'application/octet-stream'. (Currently OpenAPI-generator does not tell Swashbuckle the expected content type, or the response type, at all). Either a specific 'application/octect-stream' decorator (see https://stackoverflow.com/questions/41141137/how-can-i-tell-swashbuckle-that-the-body-content-is-required) or a more generic decorator in the style suggested at https://stackoverflow.com/questions/34990291/swashbuckle-swagger-how-to-annotate-content-types is needed.

Thus, there would be three steps to implement this scheme:

  1. Create new .mustache files to generate the BinaryPayloadAttribute and BinaryPayloadFilter (or equivalent generic classes). The same files can be used for ASP .Net Core 2.0 and 2.1.
  2. Decorate controller methods appropriately in controller.mustache.
  3. In Startup.mustache, add the new filter:
    // Support binary payloads.
    c.OperationFilter();
@grmcdorman
Copy link
Author

A more generic mechanism would be to fully support specifying Consumes (Content-Type header) and Produces (Accepts header) in the generated code. While apparently not directly supported by Swashbuckle, it is straightforward to do this using attributes and filters. See https://stackoverflow.com/questions/41141137/how-can-i-tell-swashbuckle-that-the-body-content-is-required; this is for an older version of ASP .Net Core. To select all action attributes of a particular class in .Net Core 2.0 or later, ordered by a boolean attribute with True first:
var attributes = context.ApiDescription.ActionAttributes().OfType<type>().OrderByDescending(a => a.attribute_);

So:

  1. Create two nearly identical classes in resources/aspnetcore/2.x/name.mustache, let's say OpenAPIConsumesAttribute and OpenAPIProducesAttribute, each with a Consumes or Produces string attribute and a boolean Clear attribute.
  2. Create two nearly identical classes in resources/aspnetcore/2.x/Filters with similar names (i.e. OpenAPIConsumesFilter/OpenAPIProducesFilter) in which:
    var attributes = context.ApiDescription.ActionAttributes().OfType<OpenAPIConsumesAttribute>().OrderByDescending(a => a.Clear);
    foreach (var attribute in attrbutes) ... operation.Consumes.Clear() if required; operation.Consumes.Add(attribute.Consumes)
  3. In the two Startup.mustache files, where GeneratePathParamsValidationFilter is added to the operation filters also add:
    // Enable explicit specification of Consumes and Produces on operations.
    c.OperationFilter<OpenAPIConsumesFilter>();
    c.OperationFilter<OpenAPIProducesFilter>();
  4. In the two controller.mustache files, add consumes and produces like:
    {{#consumes}}[OpenAPIConsumes(Consumes: "{{mediaType}}"{{##-first}}, Clear = true...
  5. In AspNetCoreServerCodegen.java, add the four mustache files to the supportingFiles list.

When this is done, the generated server will report the OpenAPI consumes and produces list exactly as given in the source spec.

@grmcdorman
Copy link
Author

Better way of doing the attribute: it's possible to have a list of types on the attribute itself, see https://blog.kloud.com.au/2017/08/04/swashbuckle-pro-tips-for-aspnet-web-api-part-1/

Classes there can be added nearly as-is. Need to do this in the Filter, however, because the text added on the attribute is URL encoded:
operation.Consumes = attribute.ContentTypes.Select(s => System.Net.HttpUtility.UrlDecode(s)).ToList();
Change HttpUtility to WebUtility for .Net Core 2.0.

@grmcdorman
Copy link
Author

Sorry, that should be HtmlDecode, not UrlDecode.

@grmcdorman
Copy link
Author

It appears that the .Net Core 'ProducesAttribute' and 'ConsumesAttribute' can be used, provided there is an appropriate formatter for the the specified media type known to the system. This makes this far simpler; no filters or attributes are required, just put Consumes and Produces on the action.

@wing328
Copy link
Member

wing328 commented Nov 5, 2018

cc @mandrean (2017/08) @jimschubert (2017/09)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants