Recently we have migrated our project from .NET Core 2.0
to .NET Core 2.1
. As a result our Swagger documentation site stopped working. We are still able to access it. We can see the customized title and version, but there is no API documentation, just a message saying No operations defined in spec!
.
I have tried an older solution for .NET Core 2.0, but it did not help. Based on the following two articles 1 2 I have tried removing the Swagger attributes from controller methods and adding an [ApiController]
attribute above the controller class, but that did not help either. Can anyone help to solve this issue?
.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Company.Administration.Api</RootNamespace>
<AssemblyName>Company.Administration.Api</AssemblyName>
<PackageId>Company.Administration.Api</PackageId>
<Authors></Authors>
<Company>Company, Inc</Company>
<Product>Foo</Product>
<ApplicationInsightsResourceId>/subscriptions/dfa7ef88-f5b4-45a8-9b6c-2fb145290eb4/resourcegroups/Foo/providers/microsoft.insights/components/foo</ApplicationInsightsResourceId>
<ApplicationInsightsAnnotationResourceId>/subscriptions/dfa7ef88-f5b4-45a8-9b6c-2fb145290eb4/resourceGroups/Foo/providers/microsoft.insights/components/foo</ApplicationInsightsAnnotationResourceId>
<UserSecretsId>bf821b77-3f23-47e8-834e-7f72e2ab00c5</UserSecretsId>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\netcoreapp2.1\Administration.Api.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<!-- Try to set version using environment variables set by GitVersion. -->
<Version Condition=" '$(Version)' == '' And '$(GitVersion_AssemblySemVer)' != '' ">$(GitVersion_AssemblySemVer)</Version>
<InformationalVersion Condition=" '$(InformationalVersion)' == '' And '$(GitVersion_InformationalVersion)' != '' ">$(GitVersion_InformationalVersion)</InformationalVersion>
<!-- If we don't have environment variables set by GitVersion, use default version. -->
<Version Condition=" '$(Version)' == '' ">0.0.1</Version>
<InformationalVersion Condition=" '$(InformationalVersion)' == '' ">0.0.1-local</InformationalVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile>bin\Release\netcoreapp2.1\Administration.Api.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>
<PreserveCompilationContext>false</PreserveCompilationContext>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="IdentityModel" Version="3.7.0-preview1" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.6" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="2.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Connected Services" />
</ItemGroup>
</Project>
Startup.cs
using Company.Administration.Api.Controllers;
using Company.Administration.Api.Security;
using Company.Administration.Api.Services;
using Company.Administration.Api.Swagger;
using Company.Administration.Api.Swagger.Examples;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Newtonsoft.Json.Converters;
using Swashbuckle.AspNetCore.Swagger;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Net.Http;
namespace Company.Administration.Api
{
public class Startup
{
public Startup(IConfiguration configuration, ILogger<Startup> logger, IHostingEnvironment hostingEnvironment)
{
Configuration = configuration;
Logger = logger;
HostingEnvironment = hostingEnvironment;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
}
public IHostingEnvironment HostingEnvironment { get; }
public IConfiguration Configuration { get; }
public ILogger Logger { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<HttpClient>();
services.AddTransient<AuthService>();
services.AddTransient<FooAdministrationService>();
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new StringEnumConverter());
});
services.AddFooAuthentication(Configuration);
services.AddFooAuthorization();
services.AddCors();
services
.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Administration", Version = "v1" });
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var xmlPath = Path.Combine(basePath, "Administration.Api.xml");
if (File.Exists(xmlPath))
{
c.IncludeXmlComments(xmlPath);
}
else
{
Logger.LogWarning($@"File does not exist: ""{xmlPath}""");
}
string authorityOption = Configuration["IdentityServerAuthentication:Url"] ?? throw new Exception("Failed to load authentication URL from configuration.");
string authority = $"{authorityOption}{(authorityOption.EndsWith("/") ? "" : "/")}";
var scopes = new Dictionary<string, string>
{
{ "api", "Allow calls to the Foo administration API." }
};
c.AddSecurityDefinition("OpenId Connect", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{authority}connect/authorize",
TokenUrl = $"{authority}connect/token",
Scopes = scopes
});
c.DescribeAllEnumsAsStrings();
c.OperationFilter<ExamplesOperationFilter>(services.BuildServiceProvider());
})
.ConfigureSwaggerGen(options =>
{
options.CustomSchemaIds(t => t.FullName);
options.OperationFilter<SecurityRequirementsOperationFilter>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithExposedHeaders(AdministrationControllerBase.ExposedHeaders));
app.UseAuthentication();
app.UseMvc()
.UseSwagger(x => x.RouteTemplate = "api-docs/{documentName}/swagger.json")
.UseSwaggerUI(c =>
{
c.OAuthClientId("foo-administration.swagger");
c.RoutePrefix = "api-docs";
c.SwaggerEndpoint("v1/swagger.json", "Foo Administration API");
});
app.UseReDoc(options =>
{
options.RoutePrefix = "api-docs-redoc";
options.SpecUrl = "../api-docs/v1/swagger.json";
});
}
}
}
I know this has already been answered, however just thought I'd chime in for anyone who runs into this and still looking for an answer. If you try to go directly to the json file it will provide you a reason why it's not working.
Example:
In the Address Bar: https://localhost:44300/swagger/v1/swagger.json
Error Message Returned:
{"Path":"/swagger/v1/swagger.json","Started":false,"State":"Internal Server Error","Msg":"Ambiguous HTTP method for action - Controllers.ChatMessageController.ListFriends (Project.API). Actions require an explicit HttpMethod binding for Swagger 2.0"}