AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

Using odata 8 with AddVersionedApiExplorer braking the swagger page.

Open jineshpatel99 opened this issue 3 years ago • 12 comments

In my configure services if i add these services.AddVersionedApiExplorer(); it will break the swagger and saw us swagger with

'No operations defined in spec!'

if i comment out this code it works as expected i am not sure why. any ideas My configure services

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddVersionedApiExplorer();
            services.AddControllers()
                .AddOData(opt => opt.Count().Filter().Expand().Select().OrderBy().SetMaxTop(100)
                    .AddRouteComponents("api/management/v2", EdmModelBuilder.GetEdmModelV2())
                    .Conventions.Add(new CustomConvention())
                //.Conventions.Remove(opt.Conventions.First(convention => convention is MetadataRoutingConvention))
                );

            services.AddSwaggerGen(swaggerOptions =>
            {
                swaggerOptions.SwaggerDoc("v2",
                     new OpenApiInfo
                     {
                         Title = "test Management",
                         Description = "This is the Swagger documentation for the test Management API.",
                         Version = Assembly.GetEntryAssembly().GetName().Version.ToString()
                     });

                swaggerOptions.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.Http,
                    Scheme = "Bearer"
                });

                swaggerOptions.AddSecurityRequirement(new OpenApiSecurityRequirement()
                    {
                        {
                            new OpenApiSecurityScheme
                            {
                                Reference = new OpenApiReference
                                {
                                    Type = ReferenceType.SecurityScheme,
                                    Id = "Bearer"
                                },
                                Scheme = "oauth2",
                                Name = "Bearer",
                                In = ParameterLocation.Header
                            },
                            new List<string>()
                        }
                    });

                swaggerOptions.DocumentFilter<SwaggerDocumentFilter>();

                swaggerOptions.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"));
            });

            services.AddControllers(options =>
            {
                IEnumerable<ODataOutputFormatter> outputFormatters =
                    options.OutputFormatters.OfType<ODataOutputFormatter>()
                        .Where(formatter => !formatter.SupportedMediaTypes.Any());

                foreach (var outputFormatter in outputFormatters)
                {
                    outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata"));
                }

                IEnumerable<ODataInputFormatter> inputFormatters =
                    options.InputFormatters.OfType<ODataInputFormatter>()
                        .Where(formatter => !formatter.SupportedMediaTypes.Any());

                foreach (var inputFormatter in inputFormatters)
                {
                    inputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata"));
                }
            });
            AddFormatters(services);
        }

Configure

        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseSwagger(swaggerOptions =>
            {
                swaggerOptions.PreSerializeFilters.Add
                (
                    (swagger, httpReq) =>
                    {
                        swagger.Servers = new List<OpenApiServer>
                        {
                            new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}" }
                        };
                    }
                );
            });

            app.UseSwaggerUI(swaggerUIOptions =>
            {
                swaggerUIOptions.SwaggerEndpoint($"/swagger/v2/swagger.json", $"CM v2");
            });

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }```



Controller

[ApiVersion("2.0")] public class FoldersController : BaseController {}

jineshpatel99 avatar Feb 10 '22 03:02 jineshpatel99

if you send http://localhost:5000/swagger/swagger-ui-bundle.js, you can get a js file. Search 'No operations defined in spec', you can see it in the js file.

xuzhg avatar Feb 17 '22 20:02 xuzhg

can you explain it little bit more about how we can fix this? I see this file but what do we need to do after that?

jineshpatel99 avatar Feb 25 '22 15:02 jineshpatel99

@jineshpatel99 Actually, i haven't figured the 'solution' out. did you file the same issue to swagger UI and get any feedback from them?

xuzhg avatar Feb 25 '22 19:02 xuzhg

Just did

jineshpatel99 avatar Feb 28 '22 20:02 jineshpatel99

@jineshpatel99 please add a link to the issue on the other repo for traceability.

julealgon avatar Mar 02 '22 17:03 julealgon

https://github.com/swagger-api/swagger-ui/issues/7868 Link to swagger ui issue.

jineshpatel99 avatar Mar 08 '22 14:03 jineshpatel99

@jineshpatel99 this is strange scenario and setup indeed. You're using API Versioning with API Explorer support, but only the intrinsic OData setup. OData 8 is not currently supported in API Versioning, but it's finally almost here (see dotnet/aspnet-api-versioning#677). The reason this is happening is because the API Versioning extensions for API Explorer group ApiDescription results by their ApiVersion. Results that are not grouped are ultimately discarded. Since the OData endpoints will not have a corresponding group or API version, they are thrown out.

Versioned, vanilla endpoint mixed with unversioned OData endpoints combined with API Explorer feels like a bizarre setup to me. However, in the spirit of POLA, I will concede that this is not something you expect. I think it's fair API Versioning's API Explorer extensions shouldn't blindly remove results that it doesn't know what to do with.

There are at least 2 possible solutions.

Solution 1

Extend the VersionedApiDescriptionProvider class and override ShouldExploreAction. If the action is for OData and the ApiVersion matches what you expect, then return true and it will be included in the results.

You'll also need to replace the default registration of IApiDescriptionProvider for VersionedApiDescriptionProvider with your replacement implementation in the IServiceCollection.

Solution 2

Add a custom IApiDescriptionProvider that sits in front of the API Versioning extensions, captures the OData endpoints and re-adds them.

Something like:

public class MyODataApiDescriptionProvider : IApiDescriptionProvider
{
    private readonly List<ApiDescription> results = new();

   public int Order => -100; // run before API Versioning

    public void OnProvidersExecuting(ApiDescriptionProviderContext context)
    {
        results.Clear();

        for (var i = 0; i < context.Results; i++)
        {
            var result = results[i];

            if (result.ActionDescriptor == /* OData */)
            {
                results.Add(result);
            }
        }        
    }

    public void OnProvidersExecuted(ApiDescriptionProviderContext context)
    {
        for (var i = 0; i < results.Count; i++)
        {
            context.Results.Add(results[i]);
        }
    }
}

Both of these are reasonable solutions, but if you feel this should be intrinsically supported, feel free to open an issue in the API Versioning repo.

Sidebar: @xuzhg incorporated my solution and fixed the formatters in the OData 8+ release so that you don't have to remove or update them anymore.

commonsensesoftware avatar Mar 19 '22 23:03 commonsensesoftware

@commonsensesoftware Thank you very much, the second solution works for us.

jineshpatel99 avatar Mar 29 '22 13:03 jineshpatel99

Quick update. API Versioning 6.0 is currently available in preview with support for .NET 6.0, OData 8.0, and the API Explorer extensions which can be used for OpenAPI. This bug has also been fixed. If you want to use the API Explorer for non-OData routes only, you can; however, both OData and non-OData endpoints are now fully supported.

Note that there are new packages in 6.0+. You are likely interested in:

Referencing just Asp.Versioning.OData.ApiExplorer will bring in everything. If you really only want API Explorer for non-OData routes, then you'll want:

Also note that the setup now looks like:

services.AddApiVersioning()     // Core API Versioning services with support for Minimal APIs
        .AddMvc()               // API versioning extensions for MVC Core
        .AddApiExplorer()       // API version-aware API Explorer extensions
        .AddOData()             // API versioning extensions for OData
        .AddODataApiExplorer(); // API version-aware API Explorer extensions for OData

commonsensesoftware avatar Apr 09 '22 19:04 commonsensesoftware

I have setup following

services.AddControllers()
     .AddOData((opt) =>
     {
         opt.EnableQueryFeatures()
         .AddRouteComponents("api/V1", GetEdmModelV1())
         .AddRouteComponents("api/V2", GetEdmModelV2());
     });

and then

services.AddApiVersioning()     // Core API Versioning services with support for Minimal APIs
        .AddMvc()               // API versioning extensions for MVC Core
        .AddApiExplorer()       // API version-aware API Explorer extensions
        .AddOData()             // API versioning extensions for OData
        .AddODataApiExplorer(); 

Adding Odata in Versioning breaks the ability to perform select, filter etc. and Removing that breaks the swagger versioning. Any clue how to resolve this?

akshaybheda avatar Nov 21 '23 07:11 akshaybheda

#1096

akshaybheda avatar Nov 21 '23 10:11 akshaybheda

@akshaybheda Your setup is incorrect. I've provided the correct setup in #1096.

commonsensesoftware avatar Nov 21 '23 16:11 commonsensesoftware