首页 > 解决方案 > 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";
            });

        }
    }
}

标签: c#swaggerasp.net-core-2.1

解决方案


我知道这已经得到了回答,但是只是想我会为遇到这个问题并仍在寻找答案的任何人插话。如果您尝试直接转到 json 文件,它将为您提供它不工作的原因。

例子:

在地址栏中: https://localhost:44300/swagger/v1/swagger.json

返回错误信息:

{"Path":"/swagger/v1/swagger.json","Started":false,"State":"Internal Server Error","Msg":"Ambiguous HTTP method for action - Controllers.ChatMessageController.ListFriends (Project .API). 操作需要 Swagger 2.0 的显式 HttpMethod 绑定"}


推荐阅读