RicoSuter / NSwag

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

Cannot generate OpenAPI Spec from .NET Core Assembly #2265

Open anas2204 opened 5 years ago

anas2204 commented 5 years ago

I'm trying to generate OpenAPI document from .NET Core 2.2 Assembly files.

Setup I've installed NSwag studio so "nswag" command is directly available from Command prompt. I've also built using the following way as recommended:

dotnet build /p:CopyLocalLockFileAssemblies=true

Error: Command Line

The error I get after using AspNetCore2OpenApi command is:

Command: aspnetcore2openapi /assembly:TestApi.dll /documentName:OpenApi3 /output:swagger.json

System.InvalidOperationException: The command 'aspnetcore2openapi /assembly:testapi.dll /documentname:openapi3 /output:swagger.json' could not be found.
   at NConsole.CommandLineProcessor.<ProcessSingleAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NConsole.CommandLineProcessor.<ProcessAsync>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NConsole.CommandLineProcessor.Process(String[] args, Object input)
   at NSwag.Commands.NSwagCommandProcessor.Process(String[] args)

Error: NSwagStudio

image

The error displayed is:

System.NullReferenceException: Object reference not set to an instance of an object.

Runtime: NetCore22
   at CatalogApi.Extensions.HttpClientPolicyExtensions.AddPolicies(IServiceCollection services, IConfiguration configuration) in C:\Users\ansiddiq\Source\Repos\SS-ME-ES-APIC-CatalogService\Api\CatalogApi\Extensions\HttpClientPolicyExtensions.cs:line 36
   at CatalogApi.Startup.ConfigureServices(IServiceCollection services) in C:\Users\ansiddiq\Source\Repos\SS-ME-ES-APIC-CatalogService\Api\CatalogApi\Startup.cs:line 102
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at NSwag.Commands.Generation.OpenApiGeneratorCommandBase`1.CreateWebHostAsync(AssemblyLoader assemblyLoader) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\OpenApiGeneratorCommandBase.cs:line 318
   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToSwaggerCommand.RunIsolatedAsync(AssemblyLoader assemblyLoader) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\AspNetCore\AspNetCoreToOpenApiCommand.cs:line 309
   at NSwag.Commands.IsolatedCommandBase`1.IsolatedCommandAssemblyLoader`1.Run(String commandType, String commandData, String[] assemblyPaths, String[] referencePaths) in C:\projects\nswag\src\NSwag.Commands\Commands\IsolatedCommandBase.cs:line 71
   at NSwag.Commands.IsolatedCommandBase`1.<>c__DisplayClass17_0.<RunIsolatedAsync>b__0() in C:\projects\nswag\src\NSwag.Commands\Commands\IsolatedCommandBase.cs:line 61
   at System.Threading.Tasks.Task`1.InnerInvoke()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
--- End of stack trace from previous location where exception was thrown ---
   at NSwag.Commands.IsolatedCommandBase`1.RunIsolatedAsync(String configurationFile)
   at NSwag.Commands.IsolatedSwaggerOutputCommandBase`1.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\IsolatedSwaggerOutputCommandBase.cs:line 47
   at NSwag.Commands.Generation.AspNetCore.AspNetCoreToSwaggerCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\AspNetCore\AspNetCoreToOpenApiCommand.cs:line 94
   at NSwag.Commands.NSwagDocumentBase.GenerateSwaggerDocumentAsync() in C:\projects\nswag\src\NSwag.Commands\NSwagDocumentBase.cs:line 279
   at NSwag.Commands.NSwagDocument.ExecuteAsync() in C:\projects\nswag\src\NSwag.Commands\NSwagDocument.cs:line 81
   at NSwag.Commands.Document.ExecuteDocumentCommand.ExecuteDocumentAsync(IConsoleHost host, String filePath) in C:\projects\nswag\src\NSwag.Commands\Commands\Document\ExecuteDocumentCommand.cs:line 85
   at NSwag.Commands.Document.ExecuteDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\Document\ExecuteDocumentCommand.cs:line 32
   at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input)
   at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input)
   at NConsole.CommandLineProcessor.Process(String[] args, Object input)
   at NSwag.Commands.NSwagCommandProcessor.Process(String[] args) in C:\projects\nswag\src\NSwag.Commands\NSwagCommandProcessor.cs:line 56

This error seems to be in a file where I use "Polly" library in Middleware for HTTP Retry logic. There is no problem in the code as it compiles/runs fine.

Any help is appreciated!

RicoSuter commented 5 years ago

Probably in the context of the generator the ConfigureServices method throws (because eg the configuration is null) - you need to check there in which context you are (generating or serving) and not throw exceptions - i.e. exclude the polly part when generating and then it should work...

anas2204 commented 5 years ago

@RicoSuter Thanks for the update, when I commented the code for calling Polly in ConfigureServices, it indeed worked on NSwagStudio and generated the file! The issue was the appsettings.json not being present in the same output folder (when I copied it into the folder, original code worked as well).

I had a follow up: Although this seems to be working on NSwag Studio, the Command-line or NSwag.MSBuild still doesn't work for me. I change my current directory to the location of the Output folder where the DLL is present and run:

Command: aspnetcore2openapi /assembly:TestApi.dll /documentName:OpenApi3 /output:swagger.json

But get the error I mentioned in my 1st message:

System.InvalidOperationException: The command 'aspnetcore2openapi /assembly:testapi.dll /documentname:openapi3 /output:swagger.json' could not be found.

Would you know why this happens? Also, since I explicitly mention on NSwag Studio where to get published files from ("referenced assembly files"), don't we have to do that on Command-line?

RicoSuter commented 5 years ago

It’s best to configure with nswagstudio and save the config file in the proj dir, eg nswag.json and then run it with “nswag run nswag.json /runtime:NetCore22” etc

anas2204 commented 5 years ago

Thanks @RicoSuter for the suggestion. When I save the config file and run as "nswag run .. ", it works!

The NSwag.MSBuild still doesn't work when I use the same configuration file with this inserted in the ".csproj" file:

<Target Name="NSwag" AfterTargets="Build">
  <Copy SourceFiles="@(ReferencePath)" DestinationFolder="$(OutDir)References" />
  <Exec Command="$(NSwagExe_Core20) run nswag.json /variables:Configuration=$(Configuration)" />
  <RemoveDir Directories="$(OutDir)References" />
</Target>

Error:

Error MSB3073   The command "dotnet "C:\Users\ansiddiq\.nuget\packages\nswag.msbuild\13.0.2\build\../tools/NetCore22/dotnet-nswag.dll" run nswag.json /variables:Configuration=Debug,OutDir=bin\Debug\netcoreapp2.2\" exited with code -1.  CatalogApi  C:\Users\ansiddiq\Source\Repos\CatalogService\Api\CatalogApi\TestApi.csproj

Also, if I put the following recommendation in the csproj file, the project crashes:

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

Let me know if you have thoughts on these issues.

RicoSuter commented 5 years ago

Do you have a complete stack trace?

anas2204 commented 5 years ago

The error I posted is all I see when I try to compile.

acds commented 5 years ago

@RicoSuter I'm also having issues with MSBuild

I have this set up using NSwag.MSBuild 13.0.4, Ive tested the generic $(OutDir)$(TargetFileName) and it expands to bin/Debug/netcoreapp2.2/{target assembly}l, and have validated that that "IS" here the built {Target Assembly} resides; but when botnet build is run it seem to repeat the bin/Debug/netcoreapp2.2/ part.

My goal is to generate the swagger.json and then build CSharp Clients for that file. Please excuse the {} placeholders in an attempt to no disclose proprietary information.

  <Target Name="NSwag" AfterTargets="Build">
    <Exec Command="$(NSwagExe_Core22) aspnetcore2openapi /assembly:$(OutDir)$(TargetFileName) /output:swagger.json" />
    <Exec Command="$(NSwagExe_Core22) run nswag.json /variables:Configuration=$(Configuration),OutDir=$(OutDir),InputSwagger=swagger.json" />
  </Target>

if I remove the $(OutDir) part it tries to find the {Target Assembly} in the project root.

  NSwag command line tool for .NET Core NetCore22, toolchain v13.0.4.0 (NJsonSchema v10.0.21.0 (Newtonsoft.Json v11.0.0.0))
  Visit http://NSwag.org for more information.
  NSwag bin directory: /Users/{user}/.nuget/packages/nswag.msbuild/13.0.4/tools/NetCore22
  System.IO.FileNotFoundException: Could not load file or assembly '/Users/{user}/Projects/{solutions dir}/{project dir}/bin/Debug/netcoreapp2.2/bin/Debug/netcoreapp2.2/{target assembly}.dll'. The system cannot find the file specified.

  File name: '/Users/{user}/Projects/{Solution Dir}/{Project Dir}/bin/Debug/netcoreapp2.2/bin/Debug/netcoreapp2.2/{Target Assembly}.dll'
     at System.Runtime.Loader.AssemblyLoadContext.LoadFromPath(IntPtr ptrNativeAssemblyLoadContext, String ilPath, String niPath, ObjectHandleOnStack retAssembly)
     at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(String assemblyPath)
     at NSwag.Commands.IsolatedSwaggerOutputCommandBase`1.<>c__DisplayClass10_0.<LoadAssemblies>b__0(String path) in C:\projects\nswag\src\NSwag.Commands\Commands\IsolatedSwaggerOutputCommandBase.cs:line 61
     at System.Linq.Enumerable.SelectEnumerableIterator`2.ToArray()
     at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
     at NSwag.Commands.Generation.OpenApiGeneratorCommandBase`1.CreateWebHostAsync(AssemblyLoader assemblyLoader) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\OpenApiGeneratorCommandBase.cs:line 306
     at NSwag.Commands.Generation.AspNetCore.AspNetCoreToSwaggerCommand.RunIsolatedAsync(AssemblyLoader assemblyLoader) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\AspNetCore\AspNetCoreToOpenApiCommand.cs:line 309
     at NSwag.Commands.IsolatedCommandBase`1.IsolatedCommandAssemblyLoader`1.Run(String commandType, String commandData, String[] assemblyPaths, String[] referencePaths) in C:\projects\nswag\src\NSwag.Commands\Commands\IsolatedCommandBase.cs:line 71
     at System.Threading.Tasks.Task`1.InnerInvoke()
     at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
  --- End of stack trace from previous location where exception was thrown ---
     at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
  --- End of stack trace from previous location where exception was thrown ---
     at NSwag.Commands.IsolatedCommandBase`1.RunIsolatedAsync(String configurationFile)
     at NSwag.Commands.IsolatedSwaggerOutputCommandBase`1.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\IsolatedSwaggerOutputCommandBase.cs:line 47
     at NSwag.Commands.Generation.AspNetCore.AspNetCoreToSwaggerCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\AspNetCore\AspNetCoreToOpenApiCommand.cs:line 94
     at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input)
     at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input)
     at NConsole.CommandLineProcessor.Process(String[] args, Object input)
     at NSwag.Commands.NSwagCommandProcessor.Process(String[] args) in C:\projects\nswag\src\NSwag.Commands\NSwagCommandProcessor.cs:line 56

However it has randomly works and then complains it can't load a referenced assembly Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer, I guess this is because my project is somehow configured not to copy all referenced assembles to the output directory, but just wanted to validate that assumption too.

RicoSuter commented 5 years ago

Have you tried to use /project instead of /assembly with /nobuild:true ?

acds commented 5 years ago

@RicoSuter - Thanks!

TBH I had not previously known these options even existed as the documentation on NSwag/MSBuild does not mention them; however I tried this but got more errors:

Seems to Timeout ? I assume this in some way messes with the regular startup such that the WebApi can't start as its running from a different context that for example does not find app settings.json etc. such the startup does not work ?

  Output:
  AssemblyName: {Assembly}
  OutputPath: bin/Debug/netcoreapp2.2/
  PlatformTarget:
  Platform: AnyCPU
  ProjectDepsFilePath: /Users/{user}/Projects/{Solution Dir}/{Project Dir}/bin/Debug/netcoreapp2.2/{Assembly Name}.deps.json
  ProjectDir: /Users/{User}/Project/{Solution Dir}/{Project Dir}/
  ProjectRuntimeConfigFilePath: /Users/{User}/Project/{Solution Dir}/{Project Dir}/bin/Debug/netcoreapp2.2/{Assembly Name}.runtimeconfig.json
  TargetFileName: {Assembly Name}.dll
  TargetFrameworkIdentifier: .NETCoreApp
  Executing dotnet exec --depsfile /Users/{User}/Project/{Solution Dir}/{Project Dir}/bin/Debug/netcoreapp2.2/{Assembly Name}.deps.json --runtimeconfig /Users//{User}/Project/{Solution Dir}/{Project Dir}/bin/Debug/netcoreapp2.2/{Assembly Name}.runtimeconfig.json /Users/{User}/.nuget/packages/nswag.msbuild/13.0.4/tools/NetCore22/NSwag.AspNetCore.Launcher.dll /var/folders/7x/nxb_73n96rdcg6kx82k4q2180000gp/T/tmpJJDsMO.tmp /var/folders/7x/nxb_73n96rdcg6kx82k4q2180000gp/T/tmpGgpsnV.tmp {Assembly Name} /Users/{user}/.nuget/packages/nswag.msbuild/13.0.4/tools/NetCore22/
  Launcher directory: /Users/{user}/.nuget/packages/nswag.msbuild/13.0.4/tools/NetCore22
  System.InvalidOperationException: Process dotnet timed out.
     at NSwag.Commands.Generation.AspNetCore.Exe.RunAsync(String executable, IReadOnlyList`1 args, IConsoleHost console, Nullable`1 timeout)
     at NSwag.Commands.Generation.AspNetCore.AspNetCoreToSwaggerCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\AspNetCore\AspNetCoreToOpenApiCommand.cs:line 214
     at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input)
     at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input)
     at NConsole.CommandLineProcessor.Process(String[] args, Object input)
     at NSwag.Commands.NSwagCommandProcessor.Process(String[] args) in C:\projects\nswag\src\NSwag.Commands\NSwagCommandProcessor.cs:line 56

I also can not even debug the API as the build steps that get triggered then block that too. Guess I'm need to do more MSBuild research to see if it's possible to only run the NSwag target when not in the context of debugging etc.

Additional hints and suggestion appreciated.

Without the NSwag MSBuild Steps this runs/debugs as expected.

  <Target Name="NSwag" AfterTargets="Build">
    <Exec Command="$(NSwagExe_Core22) aspnetcore2openapi /project:{Assembly Name}.csproj /nobuild:true /output:Client/swagger.json" />
    <Exec Command="$(NSwagExe_Core22) run nswag.json /variables:Configuration=$(Configuration),OutDir=Client,InputSwagger=Client/swagger.json" />
  </Target>
RicoSuter commented 5 years ago

If you run with /project in an msbuild task it is important to set /nobuild to true as otherwise it will indefinitely build the project... (infinite recursion)

RicoSuter commented 5 years ago

There are samples for this in the /samples directory I think.

AndersSahlin commented 4 years ago

Probably in the context of the generator the ConfigureServices method throws (because eg the configuration is null) - you need to check there in which context you are (generating or serving) and not throw exceptions - i.e. exclude the polly part when generating and then it should work...

@RicoSuter how do you check the context in ConfigureServices to make such a decision?

happyincent commented 2 years ago

I've tried to make use of the CaptureStartupErrors configuration to generate OpenAPI/Swagger spec in spite of the ConfigureServices method throws (eg StackExchange.Redis.RedisConnectionException).

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<TStartup>();
            webBuilder.CaptureStartupErrors(true);
        });
<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <NSwagExe>dotnet nswag</NSwagExe>
  </PropertyGroup>

  <Target Name="InstallNSwagCli" AfterTargets="AfterPublish">
    <Exec Command="dotnet tool restore" />
  </Target>

  <Target Name="ASPNET2OpenApi" AfterTargets="InstallNSwagCli">
    <Exec EnvironmentVariables="ASPNETCORE_CAPTURESTARTUPERRORS=1" Command="$(NSwagExe) aspnetcore2openapi /configuration:$(Configuration) /project:$(AssemblyName).csproj /documentName:v87 /output:$(TargetDir)$(AssemblyName).openapi.json" />
  </Target>
</Project>
POnakS commented 3 weeks ago

Anyone tried to repro on latest NSwag? /assembly is not legal anymore