Did a merge to our master branch for a major release, now swashbuckle is generating an "empty" swagger.json document like this:
{"swagger":"2.0","info":{"version":"v1","title":"My API"},"paths":{},"definitions":{}}
There's hundreds of commits here, and no errors, except for the message about "No operations defined in spec!".
How does one troubleshoot when there's no errors or logs? I have dozens of endpoints that swashbuckle is ignoring.
Can you share your startup file?
sure, but most stuff is abstracted out:
```c#
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging();
services.AddOData();
services.ConfigureCors(Configuration);
//services.AddSignalR(options => { });
services.ConfigurePostgreSqlDatabaseContext(Configuration);
services.ConfigureContextAwareness(Configuration);
services.ConfigureIdentityServices(Configuration);
services.ConfigureAuthenticationServices(Configuration);
services.ConfigureCommonServices();
services.ConfigureCacheSets();
services.ConfigureSwagger(Configuration);
services.ConfigureWebApplication();
services.ConfigureKeyCdn(Configuration);
services.ConfigureEmailServices(Configuration);
services.ConfigureMappingProfiles(Configuration);
services.ConfigureMvc(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.StartLogging(Configuration);
app.InitializeCacheSets();
app.StartSwagger();
app.UseAuthentication();
app.UseStaticFiles();
//app.UseSignalR(routes => { routes.MapHub<FeedHub>("/api/feed/live"); });
app.StartMvc(env);
if (env.IsDevelopment())
{
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
var provider = scope.ServiceProvider.GetService<IApiDescriptionGroupCollectionProvider>();
var loggerFactory = scope.ServiceProvider.GetService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<Startup>();
var sb = new StringBuilder();
foreach (var descriptionGroup in provider.ApiDescriptionGroups.Items.OrderBy(x => x.GroupName))
foreach (var descriptionItem in descriptionGroup.Items.OrderBy(x => x.RelativePath,
StringComparer.OrdinalIgnoreCase))
{
sb.AppendLine($"{descriptionItem.HttpMethod} {descriptionItem.RelativePath}");
}
logger.LogInformation($"Registered Routes: \r\n{sb}");
}
}
}
}
swagger config looks like this:
```c#
public static class Swagger
{
public static IServiceCollection ConfigureSwagger(this IServiceCollection services, IConfiguration config)
{
services.AddSwaggerGen(c =>
{
c.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"MyApi.App.xml"));
c.DescribeAllEnumsAsStrings();
c.SwaggerDoc("v1", new Info {Title = "My API", Version = "v1"});
c.DescribeAllParametersInCamelCase();
c.DescribeStringEnumsInCamelCase();
c.IgnoreObsoleteProperties();
c.CustomSchemaIds(t => t.FullName);
});
return services;
}
public static IApplicationBuilder StartSwagger(this IApplicationBuilder app)
{
app.UseSwagger(options => { });
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API Documentation");
});
return app;
}
}
mvc config looks like this:
```c#
public static class Mvc
{
public static IServiceCollection ConfigureMvc(this IServiceCollection services, IConfiguration config)
{
services
.AddMvc(options => {
options.Conventions.Add(new ApiExplorerVisibilityEnabledConvention());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddOData();
// Workaround: https://github.com/OData/WebApi/issues/1177
services.AddMvcCore(options =>
{
foreach (var outputFormatter in options.OutputFormatters.OfType<ODataOutputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0))
{
outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/prs.odatatestxx-odata"));
}
foreach (var inputFormatter in options.InputFormatters.OfType<ODataInputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0))
{
inputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/prs.odatatestxx-odata"));
}
});
return services;
}
public static IApplicationBuilder StartMvc(this IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseCors("AllowAll");
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseCors(options =>
{
options.WithOrigins("http://localhost:5000", "https://dev.myapi.com", "https://myapi.com",
"https://www.myapi.com");
});
}
var builder = new ODataConventionModelBuilder();
var inflector = new Inflector.Inflector(CultureInfo.CurrentCulture);
var entityType = typeof(IEntity);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => entityType.IsAssignableFrom(p) && entityType != p)
.ToList();
foreach (var type in types)
{
builder.AddEntitySet(inflector.Pluralize(type.Name), builder.AddEntityType(type));
}
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions{
ServeUnknownFileTypes = true,
}
);
app.UseMvc(routes =>
{
routes.MapODataServiceRoute("odata", "api/odata", builder.GetEdmModel());
// Workaround: https://github.com/OData/WebApi/issues/1175
routes.EnableDependencyInjection();
routes.CreateRoutes();
});
return app;
}
private static void CreateRoutes(this IRouteBuilder routes)
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
//more routes go here.
}
}
```
Swashbuckle is built on top of ApiExplorer. I see a class there called ApiExplorerVisibilityEnabledConvention that looks suspicious. Can you share the contents?
I also see some odd ordering based on ApiDescription GroupName which makes me think you might be setting custom GroupNames in controllers. By default Swashbuckle will use that piece of metadata to determine which actions should be included in a given doc. So, this might also be the cause. As you appear to be doing quite a bit of ApiExplorer customization, I suggest you read the following which describes how SB selects actions for a given Swagger document:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md#generate-multiple-swagger-documents
class is like so:
c#
/// <summary>
/// to list out all registered routes
/// </summary>
public class ApiExplorerVisibilityEnabledConvention : IApplicationModelConvention
{
/// <summary>
/// main component of the interface
/// </summary>
/// <param name="application"></param>
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
if (controller.ApiExplorer.IsVisible == null)
{
controller.ApiExplorer.IsVisible = true;
controller.ApiExplorer.GroupName = controller.ControllerName;
}
}
}
}
I'll see what I can do to disable the api explorer stuff.
well, you're right; I ripped out the ApiExplorerVisibilityEnabledConvention and the document is no longer empty. Can you explain why? I'd like to be able to log all registered routes in dev mode, and I'd rather not have it be an either-or sort of thing.
By default, Swashbuckle uses GroupName to determine how actions should be grouped into different Swagger documents - if it’s null OR equal to the requested documentName (“v1” in your example) it will be included, otherwise it’s omitted. So, by setting GroupName to ControllerName for all actions you’re omitting all actions from the “v1” Swagger document.
May I ask why you’re doing this?
We were having some problems with 404's on some routes in conditions where we wouldn't have access to swagger.json, and needed to document/ verify that routes were properly registered. This was code suggested by SO to log that info. Looks like we'll need to rethink that strategy...
Thanks for your assistance.
In my case it was missed [ApiController] at controller definition.
It was work without it in version 2.x and it does not work in version 3.x of lib https://github.com/Microsoft/aspnet-api-versioning
I found it after debug at this line https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/28e8673e2579b406bc5120b89589aee3b5b79341/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/SwaggerGenerator.cs#L48
My solution is
services.AddApiVersioning(options =>
{
//...
options.UseApiBehavior = false;
}
Most helpful comment
In my case it was missed [ApiController] at controller definition.
It was work without it in version 2.x and it does not work in version 3.x of lib https://github.com/Microsoft/aspnet-api-versioning
I found it after debug at this line https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/28e8673e2579b406bc5120b89589aee3b5b79341/src/Swashbuckle.AspNetCore.SwaggerGen/Generator/SwaggerGenerator.cs#L48
My solution is