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

[Feature] Generate Swagger file at postbuild without running the app #541

Closed
ghost opened this issue Nov 23, 2017 · 62 comments
Closed

[Feature] Generate Swagger file at postbuild without running the app #541

ghost opened this issue Nov 23, 2017 · 62 comments

Comments

@ghost
Copy link

ghost commented Nov 23, 2017

Feature Request

It would be great to generate the Swagger file in CI without running the app. I am willing to build such a feature (either in this repo or create another tool). Using reflection, a small program could read all docs and generate the swagger file.

What are your comments about it?

@domaindrivendev
Copy link
Owner

@poulad-rbc - this is definitely a feature that's long overdue. I'm working on some significant refactors to make the overall architecture more flexible and ultimately allow this feature to "plug-in" more seamlessly. So, you might want to hold off on your PR until then.

In the meantime you could certainly start spiking into what it would take to generate the Swagger outside of the running application from a command line tool. The tricky part will be getting the complete ApiExplorer infrastructure wired up as Swashbuckle is heavily dependent on it. Easiest thing might be an in memory/test server - you could look at the current IntegrationTests for some inspiration.

When you have a design in mind, it may be prudent to talk through it before you start knocking out the PR, just so we're on the same page. Thanks

@PaulDotNet
Copy link

I did a very small experiment to get swagger in test/WebSites/Basic project. Here is the code in the main() function:

    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        // Trying to get swagger provider here
        var sw = (ISwaggerProvider) host.Services.GetService(typeof(ISwaggerProvider));
        // ... and document here. Which throws exception when it tries to add duplicate "x-purpose" in
        // the AssignOperationVendorExtensions.Apply()
        var doc = sw.GetSwagger("v1", null, "/");

        host.Run();
    }

As you can see from my comments this approach currently does not work. GetService() call breaks something and both sw.GetSwagger and host.Run() fails. If I remove GetSwagger() failure remains.
Any ideas why is this happening?

@qmfrederik
Copy link

qmfrederik commented Jan 10, 2018

@PaulDotNet Your code actually works for me; and here's how I serialize the swagger documentation:

// Serialize
var swaggerString = JsonConvert.SerializeObject(
    swaggerDoc,
    Formatting.Indented,
    new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore,
        ContractResolver = new SwaggerContractResolver(new JsonSerializerSettings())
    });

File.WriteAllText("swagger.json", swaggerString);

@qmfrederik
Copy link

Actually, the code fails on .NET Core SDK 2.1.3 and works with .NET Core SDK 2.1.4. So it looks you'd need .NET Core SDK 2.1.4 for this to work 😄 .

@awarrenlove
Copy link

awarrenlove commented Jan 18, 2018

I came across this thread looking for a similar solution, specifically to write the doc statically during a build rather than only dynamically at runtime. I used the ideas here to update my Main method to this.

public static void Main(string[] args)
{
    if (args.Length > 0 && args[0] == "swagger")
    {
        Console.WriteLine(GenerateSwagger(args));
    }
    else
    {
        BuildWebHost(args).Run();
    }
}

private static string GenerateSwagger(string[] args)
{
    var host = BuildWebHost(args.Skip(2).ToArray()); // Skip the `swagger <doc-name>` arguments provided
    var sw = (ISwaggerProvider)host.Services.GetService(typeof(ISwaggerProvider));
    var doc = sw.GetSwagger(args[1], null, "/");
    return JsonConvert.SerializeObject(
        doc,
        Formatting.Indented,
        new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            ContractResolver = new SwaggerContractResolver(new JsonSerializerSettings())
        });
}

Now I can run dotnet run to execute the application normally or dotnet run swagger v1 to generate the Swagger doc to standard out, letting me redirect that to wherever I want during the build.

Edit: I should mention this is using the newer aspnetcore2.0 template that has the separate BuildWebHost method defined.

@PaulDotNet
Copy link

private static string GenerateSwagger(IWebHost host, string docName)
{
    var sw = (ISwaggerProvider)host.Services.GetService(typeof(ISwaggerProvider));
    var doc = sw.GetSwagger(docName, null, "/");
    return JsonConvert.SerializeObject(
        doc,
        Formatting.Indented,
        new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            ContractResolver = new SwaggerContractResolver(new JsonSerializerSettings())
        });
}

GenerateSwagger function after some cleanup. Just could not look at args parameter...

@domaindrivendev
Copy link
Owner

Preview of CLI tool is now available. See docs for more info - https://github.com/domaindrivendev/Swashbuckle.AspNetCore#dotnet-swagger-cli-tool. Please try out and provide feedback here

@tillig
Copy link

tillig commented Feb 12, 2018

It'd be nice if the tool dumped to Console.Out by default or allowed a -- sort of output parameter to avoid writing the file. That would be friendlier to things like PowerShell pipelines where additional filtering or processing during a build might take place.

@adamskoye
Copy link

@domaindrivendev this is exactly what I want, thanks for your work on it - but could you please add a link to https://myget.org/feed/domaindrivendev/package/nuget/dotnet-swagger to make it easier to find?

@domaindrivendev
Copy link
Owner

Now available on Nuget - dotnet-swagger.1.2.0-beta1

@domaindrivendev
Copy link
Owner

@ranouf
Copy link

ranouf commented Feb 20, 2018

Error occurred while restoring NuGet packages: Value cannot be null.
Parameter name: path
NU1101: Unable to find package Swashbuckle.AspNetCore.Cli. No packages exist with this id in source(s): Microsoft Visual Studio Offline Packages, nuget.org

#601

@saxonww
Copy link

saxonww commented Mar 1, 2018

This is something I want to use, but right now I don't think it can work for me.

My use case is to generate a swagger document with some extensions required to deploy to/update an api gateway as part of a deployment pipeline. These extensions contain environment configuration details which I definitely do not want to expose to end users. I don't have to use swagger for this, but all of my other options are a lot more work.

I'm doing this today with Document and Operation filters. I toggle registration of the filters and provide the extra details via command line parameters; I could use appsettings/environment variables instead. My pipeline starts the application, downloads the swagger.json, then tears it down. Doing this with a cli tool would simplify my build and probably make it a little faster.

@domaindrivendev
Copy link
Owner

This tool still uses your Startup.cs file. So if you toggle values from within it via env variables or otherwise, they should still be picked. Can you elaborate, with examples, on why you don't think it will work for your use-case.

@saxonww
Copy link

saxonww commented Mar 1, 2018

I may be mistaken. I saw no discussion of or mention of ways to pass additional command line parameters, and when I run 'dotnet swagger' I get an exception about the assembly version; it looks like this project is still on an older version of Swashbuckle. I'll try upgrading.

EDIT: I do get a failure after upgrading to 2.2.0 and trying to generate a swagger document.

/path/to/Project/Project.Startup$ addSwaggerExtensions=true dotnet swagger tofile --output swagger.json ../output/Project.Startup.dll projectDoc
[18:29:34 INF] Starting up
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
      User profile is available. Using '/home/user/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.

Unhandled Exception: System.ArgumentException: An item with the same key has already been added. Key: x-my-operation-extension
   at System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(Object key)
   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at Common.Swashbuckle.OperationFilter.Apply(Operation operation, OperationFilterContext context) in /path/to/Common/Common.Swashbuckle/OperationFilter.cs:line 42
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreateOperation(ApiDescription apiDescription, ISchemaRegistry schemaRegistry)
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreatePathItem(IEnumerable`1 apiDescriptions, ISchemaRegistry schemaRegistry)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath, String[] schemes)
   at Swashbuckle.AspNetCore.Cli.Program.<>c.<Main>b__0_3(IDictionary`2 namedArgs)
   at Swashbuckle.AspNetCore.Cli.CommandRunner.Run(IEnumerable`1 args)
   at Swashbuckle.AspNetCore.Cli.CommandRunner.Run(IEnumerable`1 args)
   at Swashbuckle.AspNetCore.Cli.Program.Main(String[] args)

Without addSwaggerExtensions=true I am able to generate the swagger document. If just start the application with addSwaggerExtensions=true, I can view swagger-ui and download a swagger.json with the expected content.

So this is going to work for me, but right now I'm either still running the tool incorrectly, I need to make other adjustments with the upgrade to 2.2.0, or there is a bug with the tool.

@domaindrivendev
Copy link
Owner

I assume PS has a way to pass environment variables to any command but you’ll have to Google the exact syntax. On Linux, you would do the following:

FOO=BAR dot net swagger

Re the exception, can you provide the stack trace?

@domaindrivendev
Copy link
Owner

Looks like you’re close - let me know how it goes?

@saxonww
Copy link

saxonww commented Mar 1, 2018

I updated my comment with the stack trace. It looks like it's coming from my code, but it only occurs when running dotnet swagger.

@saxonww
Copy link

saxonww commented Mar 2, 2018

What this boils down to is that CreateSwaggerProvider() is getting called twice, once during BuildWebHost() and again by GetRequiredService() in the Cli tool. I'm not sure what the correct fix for this really is - I am not very experienced with these DI frameworks - but I will send a PR with a potential fix.

@x2764tech
Copy link

x2764tech commented Mar 16, 2018

@domaindrivendev is this still the right place to report issues with the command line tools?

I have a space in my profile path (C:\Users\David Kemp\) and I get the following error:

dotnet exec needs a managed .dll or .exe extension. The application specified was 'C:\Users\David'"

I think this is from the call to dotnet exec not wrapping the file path in double-quotes.

I can verify this by running dotnet exec with the correct path to the cli assembly.

@twgraham
Copy link

twgraham commented Mar 20, 2018

Love your work @domaindrivendev, my plan is to use this tool in conjunction with Atlassian's openapi-diff tool as sort of sanity test. During CI builds, my frontend SPA can compare the current API with a pre-generated version it has - thus ensuring that there are no breaking changes that my frontend might not be handling.

As for feedback, the main issue I'm having right now is getting it to work with XML comments enabled. Regular Swashbuckle.AspNetCore running on the server with comments works fine.

EDIT: My issue was self inflicted - I was trying to get the name of the file from the IHostingEnvironment ApplicationName.

@fiksen99
Copy link

We're using ASP.NET Core with .NET Framework runtime, is there a plan to make this tool compatible with projects targeting .NET Framework?

@x2764tech
Copy link

@fiksen99 if you look at the https://github.com/domaindrivendev/Swashbuckle project, it looks like Richard doesn't have the time to maintain both versions.

I'm sure he'd appreciate a Pull Request for someone willing to port it though ;)

@fiksen99
Copy link

fiksen99 commented Mar 27, 2018 via email

@x2764tech
Copy link

It might be worth looking at how the dotnet-ef tool deals with this (although it does compilation too), as it has the ability to boostrap from aspnet core configuration/BuildWebHost the same as this cli would need to.

@fiksen99
Copy link

Looks like https://www.natemcmaster.com/blog/2017/11/11/build-tools-in-nuget/ may provide insight into how we can get it working as a .Net Framework build tool. Will see if I can figure anything out here

@bdyer64
Copy link

bdyer64 commented Jan 15, 2019

I am having a problem getting XML comments into the swagger generated by the CLI command. When I load the swagger via the HTTP endpoint it all works great but when I generate the swagger via the command line no annotations from the XML comments. I see a comment above that my Startup class gets called but I don't understand how that happens given how the builder is created but I am just learning .Net Core so my knowledge is limited. Any help would be appreciated.

@thomasvdb
Copy link

thomasvdb commented Mar 26, 2019

I'm having an issue when already reading a configuration section in the ConfigureServices.
Example:

var oAuthConfiguration = Configuration.GetSection("OAuthConfiguration").Get<OAuthConfiguration>();

services.AddAuthentication(options =>
{
     options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
     options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
     options.Authority = oAuthConfiguration.Authority;
     options.Audience = oAuthConfiguration.Audience;
});

I get a NullReferenceException on oAuthConfiguration.
If I remove this section, the generation of the swagger.json succeeds.

The section OAuthConfiguration is only configured in the appsettings.Development.json not in the appsettings.json. If I add it to appsettings.json as well, it also works.

Is this use-case supported or am I missing something?

@thomasvdb
Copy link

This also seems to work: ASPNETCORE_ENVIRONMENT=Development dotnet swagger tofile ...

@winstonhenke-work
Copy link

With version 4.0.1 of the tool I was not able to make the --basepath option work. It generated with the value of ApplicationPath from the appsettings.json file instead.

@KasperHoldum
Copy link

After upgrading from 5.0.0-beta to 5.0.0-RC2 we started getting the following error in jenkins:

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'System.Threading, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

 at System.Console.get_Out()

 at Swashbuckle.AspNetCore.Cli.Program.Main(String[] args)

error MSB3073: The command "dotnet swagger tofile --serializeasv2 --output "C:\jenkins\workspace\ices_abe\src\aaa\swagger.json" "C:\jenkins\workspace\ices_abe\src\aaa\bin\Release\netcoreapp2.2\aaa.dll" "v1"" exited with code 255.

If I revert back to the beta it starts working again.

@thomasvdb
Copy link

@KasperHoldum This issue is also logged here with some more details: #1103

@Indigo744
Copy link

Hello,

We are having issue when trying to generate swagger.json:

> dotnet swagger tofile bin\Debug\net472\App.exe v1

A JSON parsing exception occurred in [App\bin\Debug\net472\App.exe]: * Line 1, Column 2 Syntax error: Malformed token
A fatal error was encountered. The library 'hostpolicy.dll' required to execute the application was not found in 'App\bin\Debug\net472\'.
Failed to run as a self-contained app. If this should be a framework-dependent app, specify the appropriate framework in App\bin\Debug\net472\App.json.

After some research, I think it's because our projet is targeting the .net Framework (<TargetFramework>net472</TargetFramework> in csproj) and not a core framework.

What are our options to be able to generate the swagger json file from command line? Is this possible?

Thanks a lot for your help.

@fiksen99
Copy link

fiksen99 commented Aug 1, 2019

@Indigo744 I can share our solution to this shortly

@Indigo744
Copy link

@fiksen99 We went with the route proposed by @PaulDotNet (#541 (comment)) and we simply call our generated Exe app with the relevant args to get the Swagger Json file.

However, I'm interested in hearing your solution.

@fiksen99
Copy link

fiksen99 commented Aug 2, 2019 via email

@Indigo744
Copy link

Thanks a lot, but I think we are ok. It works, even though I would have preferred to use the CLI tool. Thanks anyway!

@woeterman94
Copy link

Does this work for .NET core 3.1?

the CLI does not seem to be compatible

@chlsmith
Copy link

chlsmith commented Apr 9, 2020

I'm finding the same.....errors every time with 3.1

@jkketts
Copy link

jkketts commented Apr 16, 2020

Ran into the hostpolicy.dll issue this evening. Turns out I was only providing the assembly path and not the actual assembly name. To get the command to run, I just ran the following in my bin/Debug/netcoreapp3.1 path:

dotnet swagger tofile ./MyApp.dll v1

@Kavyeshs41
Copy link

Kavyeshs41 commented Jul 7, 2021

I'm having an issue running CLI. The app reads some variables from either environment or the appsettings.json file. But it seems when running commands it doesn't read the appsettings.json file hence throwing a null reference error. If I set those as env variables it works just fine.

P.S. I've also tried setting ASPNETCORE_ENVIRONMENT=Development with having all settings stored in appsettings.Development.json file. but it also not working.

I'm using .NETCoreApp Version=v3.1

Set of command I'm using.

dotnet restore src/myApp/myApp.csproj
dotnet publish src/myApp/myApp.csproj -o publish/ -c Release
dotnet new tool-manifest --force
dotnet tool install --local Swashbuckle.AspNetCore.Cli --version 6.1.4
dotnet swagger tofile --output swagger.json publish/myApp.dll v1

and Error is:

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

I'm sure this is because my app is not able to find env variables.

Thanks.

@woeterman94
Copy link

woeterman94 commented Jul 12, 2021

Does this work for .NET core 3.1?

the CLI does not seem to be compatible

Okay I fixed it by using the correct versions of the packages. (I wrote a detailed article here ). Should work with the Swashbuckle.AspNetCore.Cli package

@nikolay-dimitrov
Copy link

@Indigo744 or @fiksen99 can someone from you guys share the implemented solution - i have a task to generate swagger .json files from multiple projects all created on .net framework 4.8 and those jsons files to be merged in one swagger file used by .net core API which will behave as API Gateway

@fiksen99
Copy link

fiksen99 commented Jan 5, 2022

@nikolay-dimitrov unfortunately I don't have access to the source code any more(changed company). From what I recall, we basically introduced a separate argument which would output the swagger, then called that from an ms build task. Unfortunately I don't have much more info. Sounds like you'll need to do some post processing to get the swagger files merged also

@nikolay-dimitrov
Copy link

@fiksen99 tnx for you answer - im not sure how exactly to get the instance of the swagger, in the example that is mention by @Indigo744 the following code is modified somehow

    private static string GenerateSwagger(IWebHost host, string docName)
    {
        var sw = (ISwaggerProvider)host.Services.GetService(typeof(ISwaggerProvider));
        var doc = sw.GetSwagger(docName, null, "/");
        return JsonConvert.SerializeObject(
            doc,
            Formatting.Indented,
            new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                ContractResolver = new SwaggerContractResolver(new JsonSerializerSettings())
            });
    }

but since im on .net framework 4.8 there is no IWebHost host (Swagger it self is been plugged as middleware in .NET Core) while in the normal framework when Swagger package is been installed then its magically called runtime - without any DI or something else. Any help is really appreciated

@Indigo744
Copy link

Hello,

Just to be sure I'm understanding correctly, you are using ASPNET Core on .Net Framework, right?

What we essentially do is:

public static class Program
{
    public static void Main(string[] args)
    {
        var webHost = CreateWebHostBuilder().Build();
        
        // If args, then it's for generating swagger and exit
        // otherwise run web server
        if (args.Length > 0)
        {
            Console.WriteLine(GenerateSwagger(webHost, args[0]));
            Environment.Exit(0);
        }
        
        webHost.Run();
    }
    
    private static IWebHostBuilder CreateWebHostBuilder()
    {
        IWebHostBuilder builder = WebHost
            .CreateDefaultBuilder()
            .UseConfiguration(...)
            .UseUrls(...)
            .UseStartup<Startup>()
            ....;
        
        return builder;
    }
    
    private static string GenerateSwagger(IWebHost host, string docName)
    {
        ISwaggerProvider sw = (ISwaggerProvider)host.Services.GetService(typeof(ISwaggerProvider));
        OpenApiDocument doc = sw.GetSwagger(docName);
        return doc.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0);
    }
}

Hope it helps.

@nikolay-dimitrov
Copy link

@Indigo744 no its normal .net framework 4.8 web api

@Indigo744
Copy link

@nikolay-dimitrov you mean ASP.NET? I fear you're in the wrong repo then, as this is Swagger for ASP.NET Core... Maybe you should look into https://github.com/domaindrivendev/Swashbuckle.WebApi and open an issue there.

Anyway, we don't use ASP.Net so I can't help you further. Good luck!

@fiksen99
Copy link

fiksen99 commented Jan 6, 2022

@Indigo744 this looks very familiar and likely exactly what we did. However As @Indigo744 says - this issue (and this repo) is tracking Swashbuckle in ASP.NET Core (which you can use with .NET Framework). the ASP.NET Swashbuckle version is linked out, though I believe it is no longer maintained.

@Nothing-Works
Copy link

I am using dotnet swagger tofile --output [output] [startupassembly] [swaggerdoc] this command to generate swagger docs, and it works fine locally.

But when it's in the CI/CD, as far as I know, it needs to start the app, but the app will not start in CI/CD due to some dependencies problems. So the app will throw exceptions inside Program.Main when it's run on CI/CD.

my question is, what's the recommended solution there? is there a way to skip exceptions? or even do not start the app? or should I configure what dependency to load in Main?

@markwalsh-liverpool
Copy link

@awarrenlove Are you executing dotnet run swagger as part of a post-build script within the csproj; if so, how are you doing that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests