Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET 9 - OpenAPI Not Generating Output #58805

Closed
1 task done
sbwalker opened this issue Nov 5, 2024 · 19 comments
Closed
1 task done

.NET 9 - OpenAPI Not Generating Output #58805

sbwalker opened this issue Nov 5, 2024 · 19 comments
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update.

Comments

@sbwalker
Copy link

sbwalker commented Nov 5, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I am trying to integrate OpenAPI into an existing Blazor project which was recently upgraded to .NET 9.

I added the following project reference to the Server project:

    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0-rc.2.24474.3" />

I modified the Startup.cs (yes this project has never migrated to the "new" Program.cs approach - but that should not affect the result):

        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddMvc();
            services.AddRazorPages();
            services.AddOpenApi();
            ...
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
        {
            ...
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapOpenApi();
            });

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

Note that this application is using traditional Controllers and Razor Pages - not minimal APIs.

When I run the application and browse to /openapi/v1.json I get a blank screen with no output:

Image

The Visual Studio console contains the following information:

Microsoft.AspNetCore.Routing.EndpointMiddleware: Information: Executing endpoint 'HTTP: GET /openapi/{documentName}.json'
Microsoft.AspNetCore.Routing.EndpointMiddleware: Information: Executed endpoint 'HTTP: GET /openapi/{documentName}.json'
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished HTTP/1.1 GET http://localhost:44357/openapi/v1.json - 200 - - 981.2280ms

Expected Behavior

Browsing to /openapi/v1.json should display the Open API document

Steps To Reproduce

No response

Exceptions (if any)

Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll

.NET Version

9.0.0-rc.2.24473.5

Anything else?

Note that this application already had Swagger integrated previously:

            services.AddSwaggerGen(options =>
            {
                options.CustomSchemaIds(type => type.ToString()); // Handle SchemaId already used for different type
            });
            services.TryAddSwagger(_useSwagger);

...

            if (_useSwagger)
            {
                app.UseSwagger();
                app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/" + Constants.Version + "/swagger.json", Constants.PackageId + " " + Constants.Version); });
            }

and the Swagger UI still works fine:

Image

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Nov 5, 2024
@sbwalker sbwalker changed the title .NET 9 - OpenAPI Not Generating OutPut .NET 9 - OpenAPI Not Generating Output Nov 5, 2024
@martincostello martincostello added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Nov 5, 2024
@BrennanConroy
Copy link
Member

BrennanConroy commented Nov 5, 2024

Please turn on more logging (debug/trace) so you can see the exception details, or set a breakpoint on InvalidOperationException in VS so you can see where the exception is coming from.

@sbwalker
Copy link
Author

sbwalker commented Nov 5, 2024

the actual exception is:

Unsupported parameter source: ModelBinding
Microsoft.AspNetCore.OpenApi
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.<GetParametersAsync>d__28.MoveNext()
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.<GetOperationAsync>d__21.MoveNext()
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.<GetOperationsAsync>d__20.MoveNext()
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.<GetOpenApiPathsAsync>d__19.MoveNext()
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.<GetOpenApiDocumentAsync>d__12.MoveNext()
   at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<<MapOpenApi>b__0>d.MoveNext()
   at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F56B68D2B55B5B7B373BA2E4796D897848BC0F04A969B1AF6260183E8B9E0BAF2__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass2_0.<<MapGet0>g__RequestHandler|5>d.MoveNext()
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<<Invoke>g__AwaitRequestTask|7_0>d.MoveNext()
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.<Invoke>d__5.MoveNext()
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.<Invoke>d__6.MoveNext()
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.<Invoke>d__11.MoveNext()
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.<Invoke>d__6.MoveNext()
   at Oqtane.Infrastructure.JwtMiddleware.<Invoke>d__2.MoveNext() 
   in C:\Source\Projects\oqtane.framework\Oqtane.Server\Infrastructure\Middleware\JwtMiddleware.cs:line 88

Line 88 in Jwtmiddleware is simply:

if (_next != null) await _next(context);

@danroth27
Copy link
Member

@captainsafia @mikekistler

@captainsafia
Copy link
Member

@sbwalker Are there any controller actions in your application that take advantage of custom model binding from the route and/or query parameter as opposed to the body?

@sbwalker
Copy link
Author

Yes there are controller methods that rely on query parameters ie.

        // GET: api/<controller>?siteid=x
        [HttpGet]
        public IEnumerable<Page> Get(string siteid)

@captainsafia
Copy link
Member

@sbwalker Those should be totally fine since you're just using the string type which uses the standard binding rules.

Is there a chance that you're able to provide a repro for this? My hunch is that you're seeing the same issue as #59013.

@captainsafia captainsafia added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Nov 27, 2024
@trevonmckay
Copy link

I too am not getting any output, just ERR_EMPTY_RESPONSE. I don't see any exceptions in the output log though.

services
    .AddControllers(options =>
    {
        options.Conventions.Add(new ContentNegotiationActionModelConvention());
        options.InputFormatters.Insert(0, JsonPatchExtensions.GetJsonPatchInputFormatter());
        options.ReturnHttpNotAcceptable = false;
    })
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new TypeDiscriminatingConverter());
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
        options.JsonSerializerOptions.Converters.Add(new NonPublicConstructorJsonConverterFactory());
        options.JsonSerializerOptions.Converters.Add(new JTokenConverterFactory());
        options.JsonSerializerOptions.Converters.Add(new JsonPatchDocumentConverterFactory());
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

        options.JsonSerializerOptions.MakeReadOnly();
    });

services.AddOpenApi();
app.UseEndpoints(endpoints =>
{
    endpoints.MapOpenApi("/openapi/api.json");

    endpoints.MapControllers();

    endpoints.MapHealthChecks(
        "/healthz/live",
        new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
        {
            Predicate = (_) => false,
        })
    .ShortCircuit();

    endpoints.MapHealthChecks(
        "/healthz/ready",
        new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
        {
            Predicate = healthCheck => healthCheck.Tags.Contains("ready"),
        },
        TimeSpan.FromSeconds(30));

    endpoints.MapHealthChecks(
        "/health",
        new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
        {
            ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
        });
});

Image

@trevonmckay
Copy link

trevonmckay commented Dec 1, 2024

After disabling Just My Code I am now seeing the exception

System.InvalidOperationException: 'Cannot resolve scoped service 'Microsoft.Extensions.Options.IOptionsSnapshot`1[Microsoft.AspNetCore.OpenApi.OpenApiOptions]' from root provider.'
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(ServiceCallSite callSite, IServiceScope scope, IServiceScope rootScope) in Microsoft.Extensions.DependencyInjection.ServiceLookup\CallSiteValidator.cs:line 31

@trevonmckay
Copy link

I add this just to test and I still receive the "Cannot resolve scoped service 'Microsoft.Extensions.Options.IOptionsSnapshot`1[Microsoft.AspNetCore.OpenApi.OpenApiOptions]" error

services.AddOpenApi();

services.AddScoped<IOptionsSnapshot<OpenApiOptions>>(sp =>
{
    OpenApiOptions options = new();
    return new StaticOptions<OpenApiOptions>(options);
});

@BoerG-RDW
Copy link

BoerG-RDW commented Dec 2, 2024

I am facing the same issue as @trevonmckay . I eventually found out that when I set the ASPNETCORE_ENVIRONMENT to Development, you get the exception. If you set it to Production, everything runs fine.

I'm using the latest 9.0.0 versie that was released (as of today) 20 days ago.

@mikekistler
Copy link
Contributor

I tried recreating this problem and was unsuccessful. What I did:

  • dotnet new blazorserver
  • dotnet add package Microsoft.AspNetCore.OpenApi
  • Added the two lines in Program.cs to add OpenApi services and map the openapi endpoint.

Running this project produces a (rather empty) OpenAPI document at /openapi/v1.json.

Is there something I can do in this project to recreate the problem or do I need to take a completely different path?

@danroth27
Copy link
Member

@trevonmckay @BoerG-RDW Can you provide a simplified GitHub repo that reproduces the problem you're seeing?

@BoerG-RDW
Copy link

BoerG-RDW commented Dec 3, 2024

Really strange. I'm still not sure what is causing it, but I threw out everything that I didn't need, made a copy of only the source code + project/solution file. The copy worked fine. Finally had a look at the hidden directories and when I threw out the .vs directory, it started working suddenly. It's mostly binary stuff inside that directory, so I can't really see what the actual problem is, but throwing out that directory fixed the issue for me. Visual Studio will recreate it automatically, so I don't think you'll loose anything if you throw it out.

The config directory inside the .vs/<projectname> is at least not the problem. The files are the same between a project that works and one that doesn't. I've got the feeling it is something in the v17 directory, but I can't really see what is really inside each file since it's a lot of binary data.

@sbwalker
Copy link
Author

sbwalker commented Dec 3, 2024

@danroth27 I have provided some repro steps:

  1. Clone https://github.com/oqtane/oqtane.framework (open source project)
  2. Add the following project reference to Oqtane.Server\Oqtane.Server.csproj
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
  1. Modify Oqtane.Server\Startup.cs to add OpenApi:
        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddRazorPages();
            services.AddOpenApi();
            ...
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
        {
            ...
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapOpenApi();
            });

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapRazorPages();
            });
            ...
        }
  1. Rebuild Solution / Run

Choose SQLite as the Database option
Configure Administrator Account (Username: host / Password: P@ssw0rd / Email: host@host.com / Default Site Template)
Install

  1. Browse to http://localhost:44357/openapi/v1.json

You will see a blank screen with no output:

Image

@dotnet-policy-service dotnet-policy-service bot added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. labels Dec 3, 2024
@trevonmckay
Copy link

I've managed to get this working now with two changes

public class StaticOptions<TOptions> : IOptionsSnapshot<TOptions>
    where TOptions : class
{
    private readonly TOptions _options;

    public StaticOptions(TOptions options)
    {
        _options = options;
    }

    public TOptions Value
    {
        get
        {
            return _options;
        }
    }

    public TOptions Get(string name)
    {
        return _options;
    }
}

Manually adding the IOptionsSnapshot as a singleton

services.AddSingleton<IOptionsSnapshot<OpenApiOptions>>(sp =>
{
    OpenApiOptions options = new();
    return new StaticOptions<OpenApiOptions>(options);
});

services.AddOpenApi();

which resolved the "Cannot resolve scoped service 'Microsoft.Extensions.Options.IOptionsSnapshot`1[Microsoft.AspNetCore.OpenApi.OpenApiOptions]" error.

That revealed another error, the JSON MaxDepth was exceeded during the document generation. Increasing the max depth to 256 seemed to do the trick for that.

services.Configure<JsonOptions>(jsonOptions =>
{
    jsonOptions.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    jsonOptions.SerializerOptions.MaxDepth = 256;
    jsonOptions.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    jsonOptions.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});

@captainsafia
Copy link
Member

@danroth27 I have provided some repro steps:

  1. Clone https://github.com/oqtane/oqtane.framework (open source project)
  2. Add the following project reference to Oqtane.Server\Oqtane.Server.csproj
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
  1. Modify Oqtane.Server\Startup.cs to add OpenApi:
        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddRazorPages();
            services.AddOpenApi();
            ...
        }
       public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
       {
           ...
           app.UseEndpoints(endpoints =>
           {
               endpoints.MapOpenApi();
           });

           app.UseEndpoints(endpoints =>
           {
               endpoints.MapControllers();
               endpoints.MapRazorPages();
           });
           ...
       }
  1. Rebuild Solution / Run

Choose SQLite as the Database option Configure Administrator Account (Username: host / Password: P@ssw0rd / Email: host@host.com / Default Site Template) Install

  1. Browse to http://localhost:44357/openapi/v1.json

You will see a blank screen with no output:

Image

I was able to repro with these steps. It looks like what you're seeing is indeed the same issue as #59013 which was resolved in #59035. I can open a backport for this issue to .NET 9.

@BoerG-RDW @trevonmckay The issue you are seeing are distinct from the Oqtane issue that is reported here. I'd recommend filing another issue for that.

In the meantime, I'll close this as a dupe of #59013 and track a backport for that.

@wolfgang-hartl
Copy link

Hello everyone,

TLDR: I'm facing a Problem when separating endpoints outside of Program.cs where they don't get included in the generated openapi.json

I’m not sure where this should be posted or where it belongs. I’ll write it here for now, but please let me know if I should post it somewhere else.

I’m also having issues with OpenAPI generation. I’m using a minimal API and just experimenting with the .NET framework. I’ve followed the approach described in the documentation here and moved my route handlers into separate files.

using APIRoutes;

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

var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/hello/{id}", (int id) => "Hello World!");

TodoEndpoints.Map(app);

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}



app.Run();

However, OpenAPI does not recognize these routes and only generates the JSON file with the routes registered in program.cs. Could this be related to the bug mentioned here?

That's how the generated JSON File looks like:

{
  "openapi": "3.0.1",
  "info": {
    "title": "api-dotnet | v1",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "http://localhost:5190"
    }
  ],
  "paths": {
    "/": {
      "get": {
        "tags": [
          "api-dotnet"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/hello/{id}": {
      "get": {
        "tags": [
          "api-dotnet"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": { },
  "tags": [
    {
      "name": "api-dotnet"
    }
  ]
}
´´´ 

@captainsafia
Copy link
Member

@wolfgang-hartl Your best bet is to file an new issue with a pointer to your sample code (https://github.com/dotnet/aspnetcore/issues/new). In particular, the implementation for TodoEndpoints.Map.

@wolfgang-hartl
Copy link

@captainsafia that was fast! :-) Thanks for your response - i will do that. The Implementation is just a copy/paste from the docs mentioned, but i will write that all together and create a new issue 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update.
Projects
None yet
Development

No branches or pull requests

9 participants