Skip to content

Stream is not a File type [NSwag] #445

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

Closed
MaxDeg opened this issue Aug 8, 2017 · 19 comments
Closed

Stream is not a File type [NSwag] #445

MaxDeg opened this issue Aug 8, 2017 · 19 comments

Comments

@MaxDeg
Copy link
Contributor

MaxDeg commented Aug 8, 2017

Is there any reason why Stream type fro System.IO is not considered as a File type in json?
If no I could make a PR to resolve this.

Thanks

@RicoSuter
Copy link
Owner

The type file is nit valid in json schema - it is only allowed in swagger. How is a stream property serialized in json.net? Is it a byte array encoded as base64 (same as byte[])?

@MaxDeg
Copy link
Contributor Author

MaxDeg commented Aug 8, 2017

I don't know how it's serialized.
The issue I'm facing is that I have on controller in aspnet core that take a Stream as parameter (from body).
But Streamis not recognized as a "File" type by swagger. My search finished here: https://github.com/RSuter/NJsonSchema/blob/13ff54b32fe8cd4b9ef4f50ae4f0d6b10b7c4099/src/NJsonSchema/Generation/JsonObjectTypeDescription.cs#L203.
Is it possible to handle Stream type in addition of IFormFile or HttpPostedFile?

I hope I'm clear enough :)

@RicoSuter
Copy link
Owner

Yes. Is a stream body parameter encoded as formData? Same as HttpPostedFile?

@MaxDeg
Copy link
Contributor Author

MaxDeg commented Aug 9, 2017

In my case it's a raw binary content without formData nor multipart. And I assume it always be the case. Otherwise you could use IFormFile or HttpPostedFile type from aspnet.

@RicoSuter
Copy link
Owner

I think we have to add this special case here:

https://github.com/RSuter/NSwag/blob/master/src/NSwag.SwaggerGeneration/SwaggerGenerator.cs#L150

The question is: How is this correctly described in a Swagger spec?

As seen in swagger-api/swagger-codegen#669

        {
            "name": "BinaryData",
            "in": "body",
            "required": true,
            "schema": {
                "type": "string",
                "format": "byte"  
            }
        }

But this will not expect a binary body but a JSON string which is base64 encoded like

"ABC="

Maybe we just use body and type file:

        {
            "name": "BinaryData",
            "in": "body",
            "required": true,
            "schema": {
                "type": "file"
            }
        }

and handle this correctly in the code generators...?

@RicoSuter
Copy link
Owner

Or maybe its better to use

        "schema": {
            "type": "string",
            "format": "byte"  
        }

and consumes "application/octet-stream" as described in OAI/OpenAPI-Specification#50

"consumes": [
    "application/octet-stream"
]

@MaxDeg
Copy link
Contributor Author

MaxDeg commented Aug 9, 2017

There is a lot of change for that in the 3.0 spec but for the 2.0:

Form - Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation). This is the only parameter type that can be used to send files, thus supporting the file type.

So I assume body and type file is not correct. Indeed type: string and format: byte should be a better idea. But in that case it should be mapped to a Stream (that support byte[] and it allow streaming of request which is interesting in the case of big file)

@MaxDeg
Copy link
Contributor Author

MaxDeg commented Aug 9, 2017

Or even better

"schema": {
  "type": "string",
  "format": "binary"
}

Per swagger 2.0 spec

binary | string | binary | any sequence of octets
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md

and keeping

"consumes": [
    "application/octet-stream"
]

@RicoSuter
Copy link
Owner

"binary" is not an official format string in JSON Schema, I think we should go with "byte" and "application/octet-stream"

@MaxDeg
Copy link
Contributor Author

MaxDeg commented Aug 9, 2017

byte is supposed to be "base64 encoded characters".
But I can definitively live with it :)

@RicoSuter
Copy link
Owner

RicoSuter commented Aug 9, 2017

I see it like that:

If "consumes" is "application/json" it is "base64 encoded characters"
If "consumes" is "application/octet-stream" it is processed as binary

don't you think this is how it works?

@RicoSuter RicoSuter changed the title Stream is not a File type Stream is not a File type [NSwag] Aug 9, 2017
@RicoSuter
Copy link
Owner

Ok I have this operation:

    [HttpPost, Route("upload")]
    public async Task<byte[]> Upload([FromBody] Stream data)
    {
        using (var ms = new MemoryStream())
        {
            data.CopyTo(ms);
            return ms.ToArray();
        }
    }

but this gives me the following exception:

{"Message":"The request entity's media type 'application/octet-stream' is not supported for this resource.","ExceptionMessage":"No MediaTypeFormatter is available to read an object of type 'Stream' from content with media type 'application/octet-stream'.","ExceptionType":"System.Net.Http.UnsupportedMediaTypeException","StackTrace":"   at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)\r\n   at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)"}

How is this working?

@RicoSuter
Copy link
Owner

This is the client

var content_ = new System.Net.Http.StreamContent(data);
content_.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
request_.Content = content_;
request_.Method = new System.Net.Http.HttpMethod("POST");
request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);

var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);

@MaxDeg
Copy link
Contributor Author

MaxDeg commented Aug 10, 2017

I created a custom InputFormatter (https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters#how-to-create-a-custom-formatter-class) to bind Stream type.
It's more testable than reading directly the stream from the Request object.
An example https://stackoverflow.com/questions/41346128/model-binding-not-working-with-stream-type-parameter-in-asp-net-core-webapi-cont

@RicoSuter
Copy link
Owner

RicoSuter/NSwag@97138c8

@RicoSuter
Copy link
Owner

Can you please provide a simple handler (ideally applicable with a single attribute) so that we can add it to the ASP.NET (Core) package and add documentation for this feature?

@MaxDeg
Copy link
Contributor Author

MaxDeg commented Aug 16, 2017

What do you mean by an handler?

@RicoSuter
Copy link
Owner

A custom formatter which can be applied to an operation method with an attribute

@MaxDeg
Copy link
Contributor Author

MaxDeg commented Aug 16, 2017

The only way to add a inputformatter is by adding it in the startup class on the MvcOptions object.
It was doable before using a IResourceFilter but since aspnet/Mvc#4290 it has been removed.

I will take a look for ModelBinding if we could do it there.

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

No branches or pull requests

2 participants