Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Migrating to .NET Core 2.1 breaks Swagger UI

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";
            });

        }
    }
}
like image 869
lss Avatar asked May 31 '18 11:05

lss


People also ask

Which ASP.NET Core no longer depends on the system?

Fast: ASP.NET Core no longer depends on System. Web. dll for browser-server communication. ASP.NET Core allows us to include packages that we need for our application.

What is swagger in .NET Core?

Swagger (OpenAPI) is a language-agnostic specification for describing REST APIs. It allows both computers and humans to understand the capabilities of a REST API without direct access to the source code. Its main goals are to: Minimize the amount of work needed to connect decoupled services.


2 Answers

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"}

like image 61
Briana Finney Avatar answered Oct 07 '22 06:10

Briana Finney


With C# .Net core if you use several methods with the decorator [HttpPost] or [HttpGet] the /swagger/v1/swagger.json file cannot be generated obviously because of the ambiguity of all the POST or GET methods.

If, locally working fine but getting problem after IIS hosting then you have a problem with swagger.json relative path.

Then try this below inside ConfigureServices:

app.UseSwaggerUI(c =>
{
string swaggerJsonBasePath = string.IsNullOrWhiteSpace(c.RoutePrefix) ? "." : "..";
c.SwaggerEndpoint($"{swaggerJsonBasePath}/swagger/v1/swagger.json", "My API");
});
like image 13
Rousonur Jaman Avatar answered Oct 07 '22 07:10

Rousonur Jaman