I have the following route mapped in a Mininal API application:
app.MapGet("api/test", async Task<Results<PushStreamHttpResult, NotFound>> (bool fail) => {
if (fail) return TypedResults.NotFound();
return TypedResults.Stream(stream => WriteToStream(stream), "text/plain");
}).WithSummary("Test Streaming results");
async Task WriteToStream(Stream stream) {
// do the writing... not needed for sample code
}
I didn't find a way to generate the proper documentation for this TypedResults
type in swagger/OpenAPI UI and schemas. I can do this for other result types:
app.MapGet("api/thisworks", async Task<
Results<
Ok<ApiResult>,
BadRequest<ApiResult>,
NotFound<ApiResult>>> () => { ... }
Which produces proper documentation for the many return types like this for example:
Is there a way to automatically achieve the same with TypedResults.Stream
/ PushStreamHttpResult
?
From the swagger docs:
ApiExplorer
(the ASP.NET Core metadata component that Swashbuckle is built on) DOES NOT surface theFileResult
type by default and so you need to explicitly tell it to with the Produces attribute:[HttpGet("{filename}")] [Produces("application/octet-stream", Type = typeof(FileResult))] public FileResult GetFile(string fileName)
PushStreamHttpResult
is file result too and it seems that the same rules are applied to it also so you can use the Produces
call, for example:
app.MapGet("api/test", async Task<Results<PushStreamHttpResult, NotFound>> (bool fail) =>
.WithSummary("Test Streaming results")
.Produces(200, contentType: "text/plain" )
If all PushStreamHttpResult
are plain texts then you can try to automate it with operation filter:
// sample implementation, adjust for production
public sealed class PushStreamHttpResultFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var returnType = context.MethodInfo.ReturnType;
if (ContainsPushStream(returnType))
{
// adds plain text only if no 200 are defined
operation.Responses.TryAdd("200", new OpenApiResponse
{
Content = new Dictionary<string, OpenApiMediaType>
{
["text/plain"] = new OpenApiMediaType()
{
Schema = new OpenApiSchema()
}
}
});
}
bool ContainsPushStream(Type type)
{
if (type == typeof(PushStreamHttpResult))
return true;
if (!type.IsConstructedGenericType)
return false;
if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Task<>))
return ContainsPushStream(type.GetGenericArguments()[0]);
return type.GetGenericArguments().Any(t => t == typeof(PushStreamHttpResult));
}
}
}
And add it to filters:
builder.Services.AddSwaggerGen(o =>
{
o.OperationFilter<PushStreamHttpResultFilter>();
});