1

I'm working on a project using the Clean Architecture Template.

I'm trying to configure the Endpoints with OpenApi specifications.

Here's what I tried to implement:

Custom MapGet Extension

public static class IEndpointRouteBuilderExtensions
{
    public static IEndpointRouteBuilder MapGet(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern = "")
    {
        Guard.Against.AnonymousMethod(handler);

        builder.MapGet(pattern, handler)
            .WithName(handler.Method.Name)
            .WithOpenApi();

        return builder;
    }
}

Program.cs

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;

services.AddOpenApi();
services.AddEndpointsApiExplorer();


var app = builder.Build();
app.UseHttpsRedirection();

app.MapEndpoints(); // Maps endpoints via reflection
app.MapOpenApi();   // OpenAPI support

app.Run();

WebApplicationExtensions.cs

public static class WebApplicationExtensions
{
    public static RouteGroupBuilder MapGroup(this WebApplication app, EndpointGroupBase group)
    {
        var groupName = group.GetType().Name;

        return app
            .MapGroup($"/api/{groupName}")
            .WithGroupName(groupName)
            .WithTags(groupName)
            .WithOpenApi();
    }

    public static WebApplication MapEndpoints(this WebApplication app)
    {
        var endpointGroupType = typeof(EndpointGroupBase);
        var assembly = Assembly.GetExecutingAssembly();

        var endpointGroupTypes = assembly.GetExportedTypes()
            .Where(t => t.IsSubclassOf(endpointGroupType));

        foreach (var type in endpointGroupTypes)
        {
            if (Activator.CreateInstance(type) is EndpointGroupBase instance)
            {
                instance.Map(app);
            }
        }

        return app;
    }
}

Sample Endpoint Group: WeatherForecasts

public class WeatherForecasts : EndpointGroupBase
{
    public override void Map(WebApplication app)
    {
        app.MapGroup(this)
            .MapGet(GetWeatherForecasts);
    }

    public async Task<Ok<IEnumerable<WeatherForecast>>> GetWeatherForecasts([AsParameters] WeatherService services)
    {
        var forecasts = await services.Queries.GetWeatherForecastsAsync();
        return TypedResults.Ok(forecasts);
    }
}

The problem is: the endpoint does not appear in the OpenAPI JSON (https://localhost:7283/openapi/v1.json).

There are no errors, but the custom MapGet extension seems to silently fail or not register the route properly. The endpoint is working if I test it directly via its URL, but it's invisible to OpenAPI.

But when i try in this way:

app.MapGet("/test", ([FromServices] IWeatherForecastsQueries queries) =>
{
    return Results.Ok(new[] { new WeatherForecast() });
}).WithOpenApi();

It appears in a OpenApi specifications.

Why is the endpoint not appearing in the OpenAPI spec when using my custom MapGet extension method?

1
  • Try to remove .WithGroupName when defining group. It seems it breaks exploration Commented Jun 16 at 9:49

2 Answers 2

1

Looks like the OpenAPI package uses .WithGroupName("...") to allow the generation of multiple openapi specifications (api versioning).

see: https://github.com/dotnet/aspnetcore/issues/36414#issuecomment-917739692

Updating the WebApplicationExtensions.cs and removing .WithGroupName(groupName) brings back the endpoints in the specification file.

public static class WebApplicationExtensions
{
    public static RouteGroupBuilder MapGroup(this WebApplication app, EndpointGroupBase group)
    {
        var groupName = group.GetType().Name;

        return app
            .MapGroup($"/api/{groupName}")
            .WithTags(groupName)
            .WithOpenApi();
    }

    public static WebApplication MapEndpoints(this WebApplication app)
    {
        var endpointGroupType = typeof(EndpointGroupBase);
        var assembly = Assembly.GetExecutingAssembly();

        var endpointGroupTypes = assembly.GetExportedTypes()
            .Where(t => t.IsSubclassOf(endpointGroupType));

        foreach (var type in endpointGroupTypes)
        {
            if (Activator.CreateInstance(type) is EndpointGroupBase instance)
            {
                instance.Map(app);
            }
        }

        return app;
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Removing .WithGroupName worked for me. Very strange bug...
0

First make sure to add the project reference below or else install via nuget package manager.

<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />

Next update you program.cs to refer OpenAPI services like below:

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;


services.AddEndpointsApiExplorer(); // Add OpenAPI services
services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment()) // Configure the HTTP request pipeline
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapEndpoints();
app.Run();

Next update the IEndpointRouteBuilderExtensions to configure OpenAPI metadata:

public static class IEndpointRouteBuilderExtensions
{
    public static IEndpointRouteBuilder MapGet(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern = "")
    {
        Guard.Against.AnonymousMethod(handler);

        builder.MapGet(pattern, handler)
            .WithName(handler.Method.Name)
            .WithOpenApi(operation =>
            {                
                operation.Summary = handler.Method.Name; // Add operation metadata
                operation.Description = $"Endpoint for {handler.Method.Name}";
                return operation;
            });

        return builder;
    }
}

Next update the endpoint group, make sure to add proper OpenAPI metadata:

public class WeatherForecasts : EndpointGroupBase
{
    public override void Map(WebApplication app)
    {
        app.MapGroup(this)
            .WithOpenApi()
            .MapGet(GetWeatherForecasts);
    }

    public async Task<Ok<IEnumerable<WeatherForecast>>> GetWeatherForecasts([AsParameters] WeatherService services)
    {
        var forecasts = await services.Queries.GetWeatherForecastsAsync();
        return TypedResults.Ok(forecasts);
    }
}

After doing the above changes check and see if your application works as expected

1 Comment

thank you for your answer but unfortunately i want only openapi and scalar no swashbuckle, swagger or nswag.

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.