1

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:

Proper swagger return types documentation

Is there a way to automatically achieve the same with TypedResults.Stream / PushStreamHttpResult ?

2
  • 1
    What is the documentation you are expecting for PushStreamHttpResult? Commented Jan 24, 2024 at 5:26
  • 1
    @GuruStron that's the question I should have asked myself! I understand now that the framework can't magically know what I was looking for. In fact, surprisingly, the documentation I was expecting was for a HTTP 200 status code with content type of "text/plain" since I will be streaming textual data from a database (which could be huge so I didn't want to store it in the server's memory). I solved the issue using the .Produces extension method. Commented Jan 24, 2024 at 14:22

2 Answers 2

1

From the swagger docs:

ApiExplorer (the ASP.NET Core metadata component that Swashbuckle is built on) DOES NOT surface the FileResult 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>();
});
Sign up to request clarification or add additional context in comments.

1 Comment

I ended up using Produces as it's not that annoying to use it in a single place :-) since there's only one single API endpoint which needs "streaming". Thank you, sir.
1

Use Produces<T>:

app.MapGet("api/test", async Task<Results<PushStreamHttpResult, NotFound>> (bool fail) => {
    if (fail) return TypedResults.NotFound();
    return TypedResults.Stream(stream => WriteToStream(stream), "text/plain");
})
    .Produces<NotFound>()
    .Produces<PushStreamHttpResult>()
    .WithSummary("Test Streaming results");

Result: enter image description here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.