Hi, I'm curious how to decorate actions which return FileStreamResult (or similar) to work with ApiExplorer. What I like to describe for example is that a particular action returns an image which could be either jpg or png. This is what I've tried:
[HttpGet("Image/{id}")]
[ProducesResponseType(typeof(byte[]), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(void), (int)HttpStatusCode.NotFound)]
public IActionResult ImageGet([FromRoute] int id)
{
// Access stream somehow, return 404 if not found
return new FileStreamResult(stream, contentType);
}
byte[] the right type for ProducesResponseType?[Produces("image/jpg", "image/png")] but this would semantically apply to all ProducesResponseTypes and wouldn't work anyway because the DefaultApiDescriptionProvider rely on OutputFormatters where none exists for "image/jpg" and "image/png"?I would be thankful to get some suggestions / guidance for this use case.
@rynowak can you answer this?
@dresel are you using swashbuckle/ahoy or just looking at API explorer in general? I'm not sure if they do anything for first class handling of files wrt to the swagger document generation.
Yeah it looks like we've got a scenario-sized hole here because formatters will block this. We might want to avoid matching with the formatters when Produces specifies a content type
Yes I'm using Swashbuckle. At the moment there is nothing built in for file handling, so I'm using an IOperationFilter to support file recognition:
public void Apply(Operation operation, OperationFilterContext context)
{
// Output
if (context.ApiDescription.SupportedResponseTypes.Any(x => x.Type == typeof(FileResult)))
{
foreach (var responseType in operation.Responses.Where(x => x.Value.Schema?.Ref == "#/definitions/FileResult"))
{
ProducesAttribute producesAttribute = (context.ApiDescription.ActionDescriptor as ControllerActionDescriptor)
.MethodInfo.GetCustomAttributes(typeof(ProducesAttribute)).Cast<ProducesAttribute>().SingleOrDefault();
if (producesAttribute != null)
{
operation.Produces = new List<string>(producesAttribute.ContentTypes);
}
else
{
operation.Produces = new[] { "application/octet-stream" };
}
responseType.Value.Schema = new Schema() { Type = "file" };
}
}
}
It's not perfect and not 100% correct or what I want but it's the best I've come up with so far. Since operation.Produces is empty I have to set this information manually.
For anyone else that comes across this, I've managed to get a fairly clean schema generated by doing the following:
[ProducesResponseType(typeof(Stream), 200)]
.AddSwaggerGen(opts => { opts.MapType<Stream>(() => new Schema { Type = "file" }); ... }
public class FileFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (context.ApiDescription.SupportedResponseTypes.Any(x => x.Type == typeof(Stream)))
{
foreach (var responseType in operation.Responses.Where(x => x.Value.Schema?.Type == "file"))
{
operation.Produces = new[] { "application/octet-stream" };
}
}
}
}
@rynowak Hi Ryan after last weeks' community standup I saw all the ApiControllerAttribute convention enhancements regarding swagger. Is there any simple way to make the FileResult light up in swagger as part of the 2.1 release?
Is there any simple way to make the FileResult light up in swagger as part of the 2.1 release?
Can you provide a little more info about what you're looking for here? Are you looking for something like the section Response That Returns a File https://swagger.io/docs/specification/describing-responses/
Yes exactly that!
Though I have no experience with the latest OpenAPI 3.0 stuff but I have been using the suggested workaround above in swashbuckle.Aspnetcore v1.x (the Stream to file mapping) with success. My question is: _would I have to find a new workaround when moving to aspnetcore 2.1 Vs. not bother because the framework has me covered._
Thanks
You should keep using your workaround for now. I'm going to un-question this issue and look into what we can do.
Two caveats:
FileResult because that doesn't help the case where you mix File(...) and NotFound(...)@Kwal Here's a version of your FileFilter with the redundant loop replaced with a condition...
public class FileFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (context.ApiDescription.SupportedResponseTypes.Any(x => x.Type == typeof(Stream))
&& operation.Responses.Any(x => x.Value.Schema?.Type == "file"))
{
operation.Produces = new[] { "application/octet-stream" };
}
}
}
Moving this to preview3.
Produces as-is if the content-type does not specify a wildcard. This should address the initial concern viz:How do I get "image/jpg" and "image/png" populated into the supported response content types for 200? I tried [Produces("image/jpg", "image/png")] ... wouldn't work anyway because the DefaultApiDescriptionProvider rely on OutputFormatters where none exists for "image/jpg" and "image/png"?
Are you looking for something like the section Response That Returns a File https://swagger.io/docs/specification/describing-responses/
I'm trying to figure out what the utility of that specific metadata is. Would flowing the content-type to the client suffice? If it is important, could we get Swagger generators to use some well established types (e.g Stream or byte[]) to infer a file response. cc @RSuter
I had a look at Swashbuckle and NSwag, and neither seems to have a way to configure a "file" response type. Not doing anything further in that area.
Most helpful comment
For anyone else that comes across this, I've managed to get a fairly clean schema generated by doing the following:
Specify _Stream_ as the returned type
Add a custom schema mapping
Add an _IOperationFilter_ to set _produces_ within the generated schema