RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.67k stars 1.23k forks source link

AspNetCoreToOpenApi. No service for type 'NSwag.Generation.IOpenApiDocumentGenerator' with .NET 8 #4669

Open provegard opened 7 months ago

provegard commented 7 months ago

Hi!

We use NSwag to generate TypeScript clients for our internal controller-based APIs. This works well with NSwag.MSBuild 13.20.0 and .NET 7.

During the .NET 8 upgrade, I have made the following changes:

When I build, I get:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.InvalidOperationException: No service for type 'NSwag.Generation.IOpenApiDocumentGenerator' has been registered.
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToOpenApiCommand.GenerateDocumentWithDocumentProviderAsync(IServiceProvider serviceProvider) in /_/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs:line 244
   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToOpenApiCommand.GenerateDocumentAsync(IServiceProvider serviceProvider, String currentWorkingDirectory) in /_/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs:line 239
   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToOpenApiGeneratorCommandEntryPoint.Process(String commandContent, String outputFile, String applicationName) in /_/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiGeneratorCommandEntryPoint.cs:line 29
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at NSwag.AspNetCore.Launcher.Program.Main(String[] args) in /_/src/NSwag.AspNetCore.Launcher/Program.cs:line 132
System.InvalidOperationException: Swagger generation failed with non-zero exit code '1'.
   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToOpenApiCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in /_/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs:line 195
   at NSwag.Commands.NSwagDocumentBase.GenerateSwaggerDocumentAsync() in /_/src/NSwag.Commands/NSwagDocumentBase.cs:line 270
   at NSwag.Commands.NSwagDocument.ExecuteAsync() in /_/src/NSwag.Commands/NSwagDocument.cs:line 67
   at NSwag.Commands.Document.ExecuteDocumentCommand.ExecuteDocumentAsync(IConsoleHost host, String filePath) in /_/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs:line 76
   at NSwag.Commands.Document.ExecuteDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in /_/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs:line 33
   at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input)
   at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input)
   at NSwag.Commands.NSwagCommandProcessor.ProcessAsync(String[] args) in /_/src/NSwag.Commands/NSwagCommandProcessor.cs:line 62

I include NSwag.MSBuild like this:

    <PackageReference Include="NSwag.MSBuild" Version="14.0.0-preview012">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

And I generate TS clients like this:

  <Target Name="NSwag" BeforeTargets="AfterBuild">
    <Exec ConsoleToMSBuild="true" ContinueOnError="true" Command="$(NSwagExe_Net80) run nswag.json /variables:Configuration=$(Configuration),TypescriptOutputPath=../../WebApp/app/generated">
      <Output TaskParameter="ExitCode" PropertyName="NSwagExitCode" />
      <Output TaskParameter="ConsoleOutput" PropertyName="NSwagOutput" />
    </Exec>

    <Message Text="$(NSwagOutput)" Condition="'$(NSwagExitCode)' == '0'" Importance="low" />
    <Error Text="$(NSwagOutput)" Condition="'$(NSwagExitCode)' != '0'" />
  </Target>

I have checked other issues such as https://github.com/RicoSuter/NSwag/issues/2387, but services.AddOpenApiDocument() doesn't compile (presumably because of how I reference NSwag.MSBuild), and it wasn't needed with .NET 7 anyway.

patel-devang commented 7 months ago

Did you find any solution @provegard ?

Update I am able to resolve this issue by adding AddOpenApiDocument in startup.cs file:

services.AddOpenApiDocument(configure =>
{
    configure.Title = "Service ";
});

If you are not able to resolve this function then don't forget to add:

<PackageReference Include="NSwag.Annotations" Version="14.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="14.0.0" />
<PackageReference Include="NSwag.MSBuild" Version="14.0.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
provegard commented 7 months ago

Since I don't want to generate an OpenAPI document (I already have that via another library), I added NSwag.Generation.AspNetCore version 14.0.0-preview012, then this:

    services.AddSingleton<IOpenApiDocumentGenerator, GeneratorWrapper>();
    ...

    private class GeneratorWrapper(IServiceProvider serviceProvider) : IOpenApiDocumentGenerator
    {
        public async Task<OpenApiDocument> GenerateAsync(string documentName)
        {
            var generator = new AspNetCoreOpenApiDocumentGenerator(new AspNetCoreOpenApiDocumentGeneratorSettings());
            return await generator.GenerateAsync(serviceProvider);
        }
    }
efanovroman commented 7 months ago

@RicoSuter it is a breaking changes and problem

efanovroman commented 7 months ago

Hi! @RicoSuter

I have the same problems when updating from version .NET 7 to .NET 8 (NSwag.MSBuild and NSwag.AspNetCore 13.20.0 to 14.0.2). The same behavior is observed on .NET 7 if updating NSwag.MSBuild and NSwag.AspNetCore from version 13.20.0 to 14.0.2.

  1. If I add the AddOpenApiDocument to the startup.cs file, then as a result of generating clients I get them for each controller, although in the aspNetCoreToOpenApi section in the apiGroupNames setting I have a group ["Sample"]. On version 13.20.0 everything worked without calling AddOpenApiDocument, and the generated clients corresponded to the "Sample" group. Attached is an example of the config apinswag.txt I understand that I can configure AddOpenApiDocument(settings => settings.ApiGroupNames = new[] { "Sample" }) in this way, but in the final product I have many such nswag configs, and many client groups. I need this to be configured at the config level, and not at the code level, as was the case in version 13.20.0.

  2. If you use the ApiVersionNeutral attribute on the controller, generation fails with an error

    System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
    12> ---> System.InvalidOperationException: The method 'get' on path '/api/application/InternalVersion' is registered multiple times.
    12>   at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.AddOperationDescriptionsToDocument(OpenApiDocument document, Type controllerType, List`1 operations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver)
    12>   at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateApiGroups(OpenApiDocumentGenerator generator, OpenApiDocument document, IGrouping`2[] apiGroups, OpenApiSchemaResolver schemaResolver)
    12>   at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateAsync(ApiDescriptionGroupCollection apiDescriptionGroups)
    12>   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToOpenApiCommand.GenerateDocumentWithDocumentProviderAsync(IServiceProvider serviceProvider) in /_/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs:line 245
    12>   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToOpenApiCommand.GenerateDocumentAsync(IServiceProvider serviceProvider, String currentWorkingDirectory) in /_/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs:line 239
    12>   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToOpenApiGeneratorCommandEntryPoint.Process(String commandContent, String outputFile, String applicationName) in /_/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiGeneratorCommandEntryPoint.cs:line 29
    12>   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
    12>   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
    12>   --- End of inner exception stack trace ---
    12>   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
    12>   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
    12>   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
    12>   at NSwag.AspNetCore.Launcher.Program.Main(String[] args) in /_/src/NSwag.AspNetCore.Launcher/Program.cs:line 132
    12>System.InvalidOperationException: Swagger generation failed with non-zero exit code '1'.
    12>   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToOpenApiCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in /_/src/NSwag.Commands/Commands/Generation/AspNetCore/AspNetCoreToOpenApiCommand.cs:line 195
    12>   at NSwag.Commands.NSwagDocumentBase.GenerateSwaggerDocumentAsync() in /_/src/NSwag.Commands/NSwagDocumentBase.cs:line 270
    12>   at NSwag.Commands.NSwagDocument.ExecuteAsync() in /_/src/NSwag.Commands/NSwagDocument.cs:line 67
    12>   at NSwag.Commands.Document.ExecuteDocumentCommand.ExecuteDocumentAsync(IConsoleHost host, String filePath) in /_/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs:line 76
    12>   at NSwag.Commands.Document.ExecuteDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in /_/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs:line 33
    12>   at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input)
    12>   at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input)
    12>   at NSwag.Commands.NSwagCommandProcessor.ProcessAsync(String[] args) in /_/src/NSwag.Commands/NSwagCommandProcessor.cs:line 62

    Controller example

    namespace DotNet.WebApi.Controllers
    {
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    
    [ApiController]
    [ApiVersionNeutral]
    [Route("api/application/[controller]")]
    public class InternalVersionController : ControllerBase
    {
        [HttpGet]
        public Task Get() => Task.CompletedTask;
    }
    }

    In the same project I have other controllers with controllers.txt With custom attributes and customized versioning. Again, this worked on version 13.20.0. I attach the generated swagger/index.html on version 13.20.0 Api_13.txt

How can I achieve the desired behavior without changing the code?

thangchung commented 6 months ago

I have got the error:

image

<PackageReference Include="NSwag.MSBuild" Version="14.0.3" PrivateAssets="All" />

Since I don't want to generate an OpenAPI document (I already have that via another library), I added NSwag.Generation.AspNetCore version 14.0.0-preview012, then this:

    services.AddSingleton<IOpenApiDocumentGenerator, GeneratorWrapper>();
    ...

    private class GeneratorWrapper(IServiceProvider serviceProvider) : IOpenApiDocumentGenerator
    {
        public async Task<OpenApiDocument> GenerateAsync(string documentName)
        {
            var generator = new AspNetCoreOpenApiDocumentGenerator(new AspNetCoreOpenApiDocumentGeneratorSettings());
            return await generator.GenerateAsync(serviceProvider);
        }
    }

This solution worked for me. But because I used Swashbuckle.AspNetCore for the Minimal API, I only want to use NSwag to generate swagger.json. In the past (.NET 6), I only need to ref to NSwag.MSBuild, and run RunPostBuildEvent to generate swagger file. I don't know if this behaviour of the new version of NSwag.MSBuild is a bug.

priestlydevcounty commented 2 months ago

Is this still not resolved? @RicoSuter

lahma commented 2 months ago

@priestlydevcounty the issue is open (see the status at the top) so you can expect it still to be... open.

priestlydevcounty commented 2 months ago

@priestlydevcounty the issue is open (see the status at the top) so you can expect it still to be... open.

It was more of a question regarding any updates on the issue. It has been 6 months and several users have posted questions that have gone unanswered for a while. Looks like a serious breaking change to me.

Any message from the repo maintainer would be nice to know that he is aware of it and prioritising it accordingly. More worryingly, I read in another opened issue that this happens on every major version?

lahma commented 2 months ago

It was more of a question regarding any updates on the issue. It has been 6 months and several users have posted questions that have gone unanswered for a while. Looks like a serious breaking change to me.

The updates to this issue (and others) can be seen here in the GitHub issue thread, there's no secret society or forum that updates the status behind the scenes.