When working in .NET, there’s a lot of room for reorganizing a codebase, but sometimes we can organize ourselves into a mess of a problem. Recently, when working on an ASP.NET Core Minimal API demo, I seemingly broke an endpoint’s OpenAPI definition, and it was no longer in the list of endpoints.

In this short post, we’ll compare two endpoints and discuss why one is different than the other, why it might break OpenAPI integrations, and how to fix it.

Let’s start with a typical Minimal API endpoint.

app.MapGet("/hello", () =>
    {
        return new Hello("World");
    })
    .WithName("Hello")
    .WithOpenApi();
    
record Hello(string Target);

Our endpoint registration contains enough metadata to determine the parameters, the return type and its structure, and the name of our endpoint. Great! That’s more than enough for ASP.NET Core to generate an OpenAPI definition entry.

Let’s do some refactoring of our handler to a local function.

Task<Hello> Handler()
{
    return Task.FromResult(new Hello("World"));
}

app.MapGet("/hello", Handler)
    .WithName("Hello")
    .WithOpenApi();

record Hello(string Target);

So far, so good! Everything still functions as expected. Now, let’s add a similar endpoint, but we’ll define an HttpContext parameter on our endpoint.

Task<Hello> HandlerWithContext(HttpContext ctx)
{
    return Task.FromResult(new Hello("World"));
}

app.MapGet("/goodbye", HandlerWithContext)
    .WithName("Goodbye")
    .WithOpenApi();

What?! Our OpenAPI endpoint is no longer being generated and is appearing on our definitions page. What happened?!

If we look at the warning generated by code analysis, we’ll see the culprit.

ASP0016: The method used to create a RequestDelegate returns Task<Hello>. RequestDelegate discards this value. If this isn’t intended then change the return type to non-generic Task or, if the delegate is a route handler, cast it to Delegate so the return value is written to the response.

Ah! We accidentally turned a method into RequestDelegate and stripped all the metadata necessary to build our OpenAPI definition. We can fix that by casting our registration to a delegate.

Task<Hello> HandlerWithContext(HttpContext ctx)
{
    return Task.FromResult(new Hello("World"));
}

app.MapGet("/goodbye", (Delegate)HandlerWithContext)
    .WithName("Goodbye")
    .WithOpenApi();

All is good in the world again.

Conclusion

Remember that while code can look very similar syntactically, you may inadvertently strip enough semantics to break ASP.NET Core’s OpenAPI integration when refactoring your Minimal API endpoints.

I hope you found this post helpful. Cheers.