NSwag icon indicating copy to clipboard operation
NSwag copied to clipboard

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

Open provegard opened this issue 1 year ago • 10 comments

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:

  • Upgrade to NSwag.MSBuild 14.0.0-preview012.
  • Change to use Net80 as runtime in nswag.json.
  • Change the NSwag invocation to use NSwagExe_Net80.

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.

provegard avatar Jan 02 '24 08:01 provegard

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>

patel-devang avatar Jan 18 '24 11:01 patel-devang

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);
        }
    }

provegard avatar Jan 18 '24 14:01 provegard

@RicoSuter it is a breaking changes and problem

efanovroman avatar Jan 22 '24 10:01 efanovroman

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?

efanovroman avatar Jan 23 '24 16:01 efanovroman

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.

thangchung avatar Mar 01 '24 08:03 thangchung

Is this still not resolved? @RicoSuter

priestlydevcounty avatar Jun 14 '24 15:06 priestlydevcounty

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

lahma avatar Jun 14 '24 15:06 lahma

@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?

priestlydevcounty avatar Jun 14 '24 16:06 priestlydevcounty

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.

lahma avatar Jun 14 '24 16:06 lahma