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

What's the easiest way to retrieve swagger.json documentation file? #559

Closed
gordey4doronin opened this issue Nov 4, 2015 · 15 comments
Closed

Comments

@gordey4doronin
Copy link
Contributor

Hello!

I'm trying to understand if it's possible to generate swagger.json documentation file manually from code? Or I need to run my WebAPI anyway?

If I need to run WebAPI anyway, is it easier just to request swagger endpoint rather than trying to generate this json manually from code?

By manually from code I mean something like this:

var swaggerConfig = new SwaggerDocsConfig();
MyWebApplication.SwaggerConfig.ConfigureSwagger(swaggerConfig);

// to retrieve httpConfig from somewhere
var httpConfig = new HttpConfiguration();

MyWebApplication.WebApiConfig.Register(httpConfig);

var generatorOptions = new SwaggerGeneratorOptions();

var versionInfoBuilder = new VersionInfoBuilder();
versionInfoBuilder.Version("v1", "MyWebApplication");

var defaultProvider = new SwaggerGenerator(
    httpConfig.Services.GetApiExplorer(),
    httpConfig.Formatters.JsonFormatter.SerializerSettings,
    versionInfoBuilder.Build(),
    generatorOptions);

SwaggerDocument swaggerDocument = defaultProvider.GetSwagger("http://localhost", "v1");

string result = JsonConvert.SerializeObject(swaggerDocument);

However, I guess this code still requires my WebAPI to be running? Because HttpConfiguration should be initialized first.

So finally what's the easiest way to retrieve swagger.json documentation file generated automatically from my WebAPI by Swagger/Swashbuckle?

To run self-hosted WebAPI, configure Swagger/Swashbuckle and request api/swagger/docs/v1?

Thanks,
Gordey

@gordey4doronin
Copy link
Contributor Author

And to have this swagger.json indented please refer #541

@Spencerooni
Copy link

+1 Would like to know this also!

@stevenkuhn
Copy link

If you are using OWIN/Katana with your WebAPI project, then it is possible to generate a swagger.json file from the project without hosting it on an actual port. Instead, you use the In-Memory hosting capabilities supplied by the Microsoft.Owin.Testing nuget package. For more on In-Memory testing, here are a couple of blog posts:

http://www.strathweb.com/2013/12/owin-memory-integration-testing/
http://www.juliencorioland.net/archives/using-owin-to-test-your-web-api-controllers

For example, I have a Startup.cs file with something like the following in my WebAPI project (I'm including Autofac to show how I also handle dependency injection, but you can ignore it if you like):

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = new HttpConfiguration();
        ConfigureAutofac(app, config);

        config.MapHttpAttributeRoutes();
        config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;

        // Configures Swagger using Swashbuckle here. For this example, the
        // API version is "v1"
        SwaggerConfig.Register(config);

        app.UseWebApi(config);
    }

    public virtual IContainer ConfigureAutofac(IAppBuilder app, HttpConfiguration config)
    {
        var builder = new ContainerBuilder();

        builder.RegisterAssemblyModules(typeof(Startup).Assembly);

        var container = builder.Build();

        // Allows the Autofac container to be accessed from the OWIN context.
        app.UseAutofacMiddleware(container);

        // Informs ASP.NET WebApi 2 to use Autofac when resolving dependencies
        // (e.g. dependencies in Controllers).
        config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

        return container;
    }
}

Then I created a new Console project called ProjectName.Api.SwaggerGenerator with a project reference to my WebAPI project and added the Microsoft.Owin.Testing NuGet package. In Program.cs I have the following code:

public class Program
{
    public static void Main(string[] args)
    {
        using (var server = TestServer.Create<ApiStartup>())
        {
            var response = server.CreateRequest("/swagger/docs/v1").GetAsync().Result;
            var content = response.Content.ReadAsStringAsync().Result;
            File.WriteAllText("swagger.json", content);
        }
    }
}

I then run the console app and a file called swagger.json is created with the output of that request. As a side note, the url of /swagger/docs/v1 might vary depending on your Swagger configuration.

With this solution, you don't need to worry about exposing your WebAPI to the public or a potential port conflict because it is all hosted in memory without requiring a port for listening to incoming requests.

If you are not using OWIN/Katana, then it might still be possible, but you would have the Startup.cs file in the SwaggerGenerator project and not in the WebAPI project. I haven't tested that though, so I cannot be sure.

Hope that helps! I've successfully used this method along with the AutoRest project to generate .NET client code for my WebAPI project every time I build my solution.

@Spencerooni
Copy link

I didn't ask the original question but thank you for the detailed response. Will try this out tomorrow.

@gordey4doronin
Copy link
Contributor Author

@chartek Thanks for the answer!
I already did almost the same thing, but didn't write about it here.
And I also use this to run AutoRest to generate NodeJS code :)

My code is pretty similar haha, except I don't use TestServer.

Actually I missed this thing about Microsoft.Owin.Testing and its capability to run in-memory.
Many thanks you pointed it out!

Initially what I wanted to know - if it's possible to generate swagger.json documentation file manually from code without running WebAPI?

It seems the final, full and correct answer is:

No, it's not possible to generate swagger.json without running WebAPI project.
The simplest way to run and generate swagger.json file is to run self-hosted OWIN WebAPI using in-memory hosting capability of Microsoft.Owin.Testing.

I also provide my code below, although it's pretty similar to @chartek code above. Hope it would help somebody.

Created the separate project to run Self-Hosted Web API using OWIN.
Here is host startup file.

namespace MyWebApi.SelfHosted
{
    public class WebApiHostStartup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            // Configure Web API for self-host.
            var config = new HttpConfiguration();

            WebApiConfig.Register(config);

            ConfigureSwagger(config);
            //ConfigureUnity(config);

            appBuilder.UseWebApi(config);
        }

        private static void ConfigureSwagger(HttpConfiguration httpConfiguration)
        {
            httpConfiguration
                .EnableSwagger(MyWebApi.SwaggerConfig.ConfigureSwagger)
                .EnableSwaggerUi(MyWebApi.SwaggerConfig.ConfigureSwaggerUi);
        }
    }
}

Swagger config is still stored in original WebAPI project.

namespace MyWebApi
{
    public class SwaggerConfig
    {
        public static void Register()
        {
            GlobalConfiguration.Configuration
                .EnableSwagger(ConfigureSwagger)
                .EnableSwaggerUi(ConfigureSwaggerUi);
        }

        public static void ConfigureSwagger(SwaggerDocsConfig c)
        {
            c.SingleApiVersion("v1", "MyWebApi");
        }

        public static void ConfigureSwaggerUi(SwaggerUiConfig c)
        {
            //c.InjectStylesheet(containingAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testStyles1.css");
            //c.InjectJavaScript(thisAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testScript1.js");
            //c.BooleanValues(new[] { "0", "1" });
            //c.SetValidatorUrl("http://localhost/validator");
            //c.DisableValidator();
            //c.DocExpansion(DocExpansion.List);
            //c.CustomAsset("index", containingAssembly, "YourWebApiProject.SwaggerExtensions.index.html");
            //c.EnableDiscoveryUrlSelector();
            //c.EnableOAuth2Support("test-client-id", "test-realm", "Swagger UI");
        }
    }
}

And running the self-hosted project from the console app entry-point.

namespace MyWebApi.SelfHosted
{
    public class Program
    {
        private const string BaseAddress = "http://localhost:8080";

        public static void Main(string[] args)
        {
            // Start OWIN host
            using (WebApp.Start<WebApiHostStartup>(BaseAddress))
            {
                using (var client = new HttpClient { BaseAddress = new Uri(BaseAddress) })
                {
                    HttpResponseMessage response = client.GetAsync("swagger/docs/v1").Result;
                    string content = response.Content.ReadAsStringAsync().Result;
                    File.WriteAllText("swagger.json", content);
                }
            }
        }
    }
}

@domaindrivendev
Copy link
Owner

If you don't mind dropping down to a lower level, forfiting some of the fluent config, you may find the following approach a bit easier:

using System.Web.Http;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Swashbuckle.Dummy;
using Swashbuckle.Swagger;
using Newtonsoft.Json;
using Swashbuckle.Application;

namespace Swashbuckle.Tests
{
    [TestFixture]
    public class Spike
    {
        [Test]
        public void GetSwagger()
        {
            var httpConfig = new HttpConfiguration();

            // 1) Apply your WebApi config.
            WebApiConfig.Register(httpConfig);
            httpConfig.EnsureInitialized();

            // 2) Generate in-memory swagger doc
            var swaggerProvider = new SwaggerGenerator(
                httpConfig.Services.GetApiExplorer(),
                httpConfig.Formatters.JsonFormatter.SerializerSettings,
                new Dictionary<string, Info> { { "v1", new Info { version = "v1", title = "My API" } } },
                new SwaggerGeneratorOptions(
                    // apply your swagger options here ...
                    schemaIdSelector: (type) => type.FriendlyId(true),
                    conflictingActionsResolver: (apiDescriptions) => apiDescriptions.First()
                )
            );
            var swaggerDoc = swaggerProvider.GetSwagger("http://tempuri.org/api", "v1");

            // 3) Serialize
            var swaggerString = JsonConvert.SerializeObject(
                swaggerDoc,
                Formatting.Indented,
                new JsonSerializerSettings
                {
                    NullValueHandling = NullValueHandling.Ignore,
                    Converters = new[] { new VendorExtensionsConverter() }
                }
            );
        }
    }
}

@gordey4doronin
Copy link
Contributor Author

@domaindrivendev Thanks for the answer! I already use self-hosted approach, but maybe I will reconsider it in future.

@floydpink
Copy link

floydpink commented Aug 18, 2016

@domaindrivendev - thank you very much for this succinct, reusable snippet.

We have been able to add more options like the securityDefinitions etc. into the SwaggerGeneratorOptions constructor - but could not figure out how config.IncludeXmlComments() (as described here) can be brought in to this format.

Is this doable? And if it is, can you please show how?

@IliaFraedom
Copy link

Hello @domaindrivendev,
I'm using your code from previous answer, but I could not figure out how I could generate xml comments as part of swaggerDoc (looks like @floydpink has same problem ) , could you help with that?

@TStream
Copy link

TStream commented Oct 24, 2016

To add xml comments using the solution @domaindrivendev gave above there is an operation filter called
ApplyXmlActionComments in Swashbuckle.Swagger.XmlComments in which you can just pass it the location of the xml file as a string

new Swashbuckle.Swagger.XmlComments.ApplyXmlActionComments("location of xml file"));

@SandeepApsingekar
Copy link

Hi,

I'm not sure whether this question sounds weird. But I was wondering when we can create a swagger.json file from our web api controller. So, is there a way where we can generate controller and all action methods automatically in my web api controller locally using the swagger.json file.

I apologize if this question sounds irrelevant. I'm just learning.

Thank you!
Best Regards,
Sandeep

@leftler
Copy link

leftler commented Aug 7, 2017

Here is the implementation we came up with. The SwaggerConfig.ConfigureSwagger(swaggerDocsConfig); call is just calling the same Action<SwagerDocsConfig> that GlobalConfiguration.Configuration.EnableSwagger(Action<SwagerDocsConfig>) is calling. It also uses Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject to get around the fact that there are no public getters for any of the fields nor a public way to get a SwaggerGeneratorOptions from a SwagerDocsConfig.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Swashbuckle.Application;
using Swashbuckle.Swagger;
using Swashbuckle.Swagger.XmlComments;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using System.Xml.XPath;

namespace Example
{
    [TestClass]
    public class SwaggerConfigTests
    {
        [TestMethod]
        public async Task SwaggerTests_ValidateSwagger()
        {
            var httpConfig = new HttpConfiguration();
            var swaggerDocsConfig = new SwaggerDocsConfig();

            // Apply WebApi config.
            WebApiConfig.Register(httpConfig);
            FilterConfig.Register(httpConfig);
            httpConfig.EnsureInitialized();

            // Apply Swagger config
            SwaggerConfig.ConfigureSwagger(swaggerDocsConfig);

            //Generate documentation.
            var swaggerString = GenerateSwaggerDocumentation(httpConfig, swaggerDocsConfig);

            using (WebClient client = new WebClient())
            {
                client.Headers["Content-Type"] = "application/json";
                var result = await client.UploadStringTaskAsync("http://online.swagger.io/validator/debug", "POST", swaggerString);

                if (result != "{}")
                {
                    Assert.Fail("Swagger Validation Failed with the following validation errors:\n{0}", result);
                }
            }
        }

        private static string GenerateSwaggerDocumentation(HttpConfiguration httpConfig, SwaggerDocsConfig swaggerDocsConfig)
        {
            var options = GetSwaggerGeneratorOptions(swaggerDocsConfig);

            var swaggerProvider = new SwaggerGenerator(
                httpConfig.Services.GetApiExplorer(),
                httpConfig.Formatters.JsonFormatter.SerializerSettings,
                new Dictionary<string, Info> { { "v1", new Info { version = "v1", title = "UnitTest" } } },
                options);

            var swaggerDoc = swaggerProvider.GetSwagger("https://example.org", "v1");
            var swaggerString = JsonConvert.SerializeObject(
                swaggerDoc,
                Formatting.Indented,
                new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = new[] { new VendorExtensionsConverter() } });
            return swaggerString;
        }

        private static SwaggerGeneratorOptions GetSwaggerGeneratorOptions(SwaggerDocsConfig swaggerDocsConfig)
        {
            var privateDoc = new PrivateObject(swaggerDocsConfig);

            Dictionary<string, SecurityScheme> dictionary = ((IDictionary<string, SecuritySchemeBuilder>)privateDoc.GetField("_securitySchemeBuilders")).Any()
                                                                ? ((IDictionary<string, SecuritySchemeBuilder>)privateDoc.GetField("_securitySchemeBuilders")).ToDictionary(
                                                                    kvp => kvp.Key,
                                                                    kvp =>
                                                                    {
                                                                        var privateBuilder = new PrivateObject(kvp.Value);
                                                                        return (SecurityScheme)privateBuilder.Invoke("Build", BindingFlags.Instance | BindingFlags.NonPublic);
                                                                    })
                                                                : null;
            List<IModelFilter> list1 = ((IList<Func<IModelFilter>>)privateDoc.GetField("_modelFilters")).Select(factory => factory()).ToList();
            List<IOperationFilter> list2 = ((IList<Func<IOperationFilter>>)privateDoc.GetField("_operationFilters")).Select(factory => factory()).ToList();
            foreach (Func<XPathDocument> xmlDocFactory in (IEnumerable<Func<XPathDocument>>)privateDoc.GetField("_xmlDocFactories"))
            {
                XPathDocument xmlDoc = xmlDocFactory();
                list1.Insert(0, new ApplyXmlTypeComments(xmlDoc));
                list2.Insert(0, new ApplyXmlActionComments(xmlDoc));
            }

            SwaggerGeneratorOptions options = new SwaggerGeneratorOptions(
                (Func<ApiDescription, string, bool>)privateDoc.GetField("_versionSupportResolver"),
                (IEnumerable<string>)privateDoc.GetField("_schemes"),
                dictionary,
                (bool)privateDoc.GetField("_ignoreObsoleteActions"),
                (Func<ApiDescription, string>)privateDoc.GetField("_groupingKeySelector"),
                (IComparer<string>)privateDoc.GetField("_groupingKeyComparer"),
                (IDictionary<Type, Func<Schema>>)privateDoc.GetField("_customSchemaMappings"),
                ((IList<Func<ISchemaFilter>>)privateDoc.GetField("_schemaFilters")).Select(factory => factory()).ToList(),
                list1,
                (bool)privateDoc.GetField("_ignoreObsoleteProperties"),
                (Func<Type, string>)privateDoc.GetField("_schemaIdSelector"),
                (bool)privateDoc.GetField("_describeAllEnumsAsStrings"),
                (bool)privateDoc.GetField("_describeStringEnumsInCamelCase"),
                (bool)privateDoc.GetField("_applyFiltersToAllSchemas"),
                list2,
                ((IList<Func<IDocumentFilter>>)privateDoc.GetField("_documentFilters")).Select(factory => factory()).ToList(),
                (Func<IEnumerable<ApiDescription>, ApiDescription>)privateDoc.GetField("_conflictingActionsResolver"));
            return options;
        }
    }
}

@fizxmike
Copy link

fizxmike commented Nov 3, 2018

Serously? The answer is 100+ lines of code? This is not maintainable: if this ends up breaking in the future, how would anyone know how to fix it without having to basically grock the entire thing and re-write it? This should have a solution in Swashbuckle project (some simple XML in .csproj to write out .json file to a location at build time).

@sdolenc
Copy link

sdolenc commented Feb 7, 2019

FYI: I know this isn't exactly the answer many people are looking for, but if you happen to be using Swashbuckle.AspNetCore then you have the option of generating the .json file from the command line without deploying

https://github.com/domaindrivendev/Swashbuckle.AspNetCore#swashbuckleaspnetcorecli

This solution was discussed in this thread domaindrivendev/Swashbuckle.AspNetCore#541 (again, it is specific to dot net core, but there might be some helpful information there as well)

@uthens
Copy link

uthens commented Aug 28, 2019

If you don't mind dropping down to a lower level, forfiting some of the fluent config, you may find the following approach a bit easier:

using System.Web.Http;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Swashbuckle.Dummy;
using Swashbuckle.Swagger;
using Newtonsoft.Json;
using Swashbuckle.Application;

namespace Swashbuckle.Tests
{
    [TestFixture]
    public class Spike
    {
        [Test]
        public void GetSwagger()
        {
            var httpConfig = new HttpConfiguration();  

            // 1) Apply your WebApi config.
            WebApiConfig.Register(httpConfig);
            httpConfig.EnsureInitialized();

            // 2) Generate in-memory swagger doc
            var swaggerProvider = new SwaggerGenerator(
                httpConfig.Services.GetApiExplorer(),
                httpConfig.Formatters.JsonFormatter.SerializerSettings,
                new Dictionary<string, Info> { { "v1", new Info { version = "v1", title = "My API" } } },
                new SwaggerGeneratorOptions(
                    // apply your swagger options here ...
                    schemaIdSelector: (type) => type.FriendlyId(true),
                    conflictingActionsResolver: (apiDescriptions) => apiDescriptions.First()
                )
            );
            var swaggerDoc = swaggerProvider.GetSwagger("http://tempuri.org/api", "v1");

            // 3) Serialize
            var swaggerString = JsonConvert.SerializeObject(
                swaggerDoc,
                Formatting.Indented,
                new JsonSerializerSettings
                {
                    NullValueHandling = NullValueHandling.Ignore,
                    Converters = new[] { new VendorExtensionsConverter() }
                }
            );
        }
    }
}

This code to support on .NET Framework, but it's not support .NET Core
How to import to .NET Core ?

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