代码之家  ›  专栏  ›  技术社区  ›  Mike

Swagger无法加载

  •  0
  • Mike  · 技术社区  · 1 年前

    我正在使用Azure函数(独立),并尝试导航到Swagger UI,但收到以下错误:

    未能加载API定义。 提取错误 未定义https://localhost:7009/api/v1/swagger.json

    经过数小时的调查,我将问题归结为这样一个事实,即我的响应模型包含一个实现 ICollection<T> 似乎当Swagger-Gen反射试图获得潜在类型时,它无法解释 ICollection<T> 作为数组类型。

    enter image description here

    下面使用OpenApi属性对问题进行简单说明,记住“OpenApiResponseWithBody”是负责描述响应模型的属性。

    [Function("GetConnector")]
    [OpenApiOperation("GetConnector", AppConst.FunctionTags.Connectors, Summary = "Get an existing Connector.")]
    [OpenApiParameter("id", Type = typeof(Guid), In = ParameterLocation.Path, Required = true, Summary = "Specifies the id of the Connector to get.")]
    [OpenApiResponseWithBody(HttpStatusCode.OK, MediaTypeNames.Application.Json, typeof(ConnectorResponse), Description = "Returns the details of the Connector.")]
    public async Task<HttpResponseData> GetConnectorAsync([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "connectors/{id}")] HttpRequestData httpReq,
        Guid id,
        CancellationToken cancellationToken)
    {
        // omitted not important
    }
    

    ConnectorResponse对象 :

    public class ConnectorResponse(Guid id, string name)
    {
        public Guid Id
        {
            get;
            set;
        } = id;
        
        public string Name
        {
            get;
            set;
        } = name;
        
        public AuthConfigCollection AuthConfigurations
        {
            get;
            set;
        } = [];
    }
    

    AuthConfigConnection是导致问题的原因。它之所以造成问题,完全是因为它实现了 ICollection<T> .

    public class AuthConfigCollection() : ICollection<AuthConfigurationDto>
    {
    private List<AuthConfigurationDto> configs = new List<AuthConfigurationDto>();
    
    public IEnumerator<AuthConfigurationDto> GetEnumerator()
    {
        return configs.GetEnumerator();
    }
    
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
    
    public void Add(AuthConfigurationDto item)
    {
        throw new NotImplementedException();
    }
    
    public void Clear()
    {
        throw new NotImplementedException();
    }
    
    public bool Contains(AuthConfigurationDto item)
    {
        throw new NotImplementedException();
    }
    
    public void CopyTo(AuthConfigurationDto[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }
    
    public bool Remove(AuthConfigurationDto item)
    {
        throw new NotImplementedException();
    }
    
    public int Count
    {
        get;
    }
    
    public bool IsReadOnly
    {
        get;
    }
    }
    

    如果将响应模型更改为以下内容(即 不是 我上面描述的)然后它就起作用了,但这不是我描述的原因。

    public class ConnectorResponse(Guid id, string name)
        {
            public Guid Id
            {
                get;
                set;
            } = id;
            
            public string Name
            {
                get;
                set;
            } = name;
            
            public List<AuthConfigurationDto> AuthConfigurations
            {
                get;
                set;
            } = [];
        }
    

    我非常具体地问为什么Swagger Gen不使用ICollection。我不是问它是否适用 List<T> ,很明显,在我调查这个问题的过程中,我进行了测试 列表<T> vs ICollection<T> .

    如果我的轮胎爆胎了,问我是否可以尝试换一种不同的汽油类型是不可行的。我试着理解 为什么? Swagger Gen有一个问题 ICollection<T> 所以我不必去重做大量的模型。

    0 回复  |  直到 1 年前
        1
  •  0
  •   Ikhtesam Afrin    1 年前

    我对下面提到的文件进行了一些更改,并得到了预期的响应。

    GetConnector -

    using _78600033.Models;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using System.Net.Mime;
    using System.Net;
    using Microsoft.OpenApi.Models;
    
    namespace _78600033
    {
        public class GetConnector
        {
            [Function("GetConnector")]
            [OpenApiOperation(operationId: "GetConnector", tags: new[] { "Connectors" }, Summary = "Get an existing Connector.")]
            [OpenApiParameter(name: "id", In = ParameterLocation.Path, Required = true, Type = typeof(Guid), Summary = "Specifies the id of the Connector to get.")]
            [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: MediaTypeNames.Application.Json, bodyType: typeof(ConnectorResponse), Description = "Returns the details of the Connector.")]
            public async Task<HttpResponseData> GetConnectorAsync(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "connectors/{id}")] HttpRequestData httpReq,
            Guid id,
            FunctionContext executionContext)
            {
                //creating a dummy response
                var connector = new ConnectorResponse(id, "Example Connector")
                {
                    AuthConfigurations = new List<AuthConfigurationDto>
                    {
                        new AuthConfigurationDto { Type = "OAuth", Config = "OAuthConfigDetails" },
                        new AuthConfigurationDto { Type = "API Key", Config = "ApiKeyDetails" }
                    }
                };
    
                var response = httpReq.CreateResponse(HttpStatusCode.OK);
                await response.WriteAsJsonAsync(connector);
    
                return response;
            }
        }
    }
    

    身份验证配置集合 -

    using _78600033.Models;
    using System.Collections;
    
    namespace _78600033
    {
        public class AuthConfigCollection : ICollection<AuthConfigurationDto>
        {
            private List<AuthConfigurationDto> configs = new List<AuthConfigurationDto>();
    
            public IEnumerator<AuthConfigurationDto> GetEnumerator()
            {
                return configs.GetEnumerator();
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
            public void Add(AuthConfigurationDto item)
            {
                if (item == null)
                {
                    throw new ArgumentNullException(nameof(item));
                }
    
                configs.Add(item);
            }
            public void Clear()
            {
                configs.Clear();
            }
            public bool Contains(AuthConfigurationDto item)
            {
                return configs.Contains(item);
            }
            public void CopyTo(AuthConfigurationDto[] array, int arrayIndex)
            {
                configs.CopyTo(array, arrayIndex);
            }
            public bool Remove(AuthConfigurationDto item)
            {
                return configs.Remove(item);
            }
            public int Count => configs.Count;
            public bool IsReadOnly => false;
        }
    }
    

    连接器响应 -

    使用 List<AuthConfigurationDto> AuthConfigurations 而不是 AuthConfigurations 在里面 ConnectorResponse 文件。

    using _78600033.Models;
    
    namespace _78600033
    {
        public class ConnectorResponse
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public List<AuthConfigurationDto> AuthConfigurations { get; set; } = new List<AuthConfigurationDto>();
    
            public ConnectorResponse(Guid id, string name)
            {
                Id = id;
                Name = name;
            }
        }
    }
    

    程序cs -

    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.OpenApi.Models;
    
    var host = new HostBuilder()
        .ConfigureFunctionsWebApplication()
        .ConfigureServices(services =>
        {
            services.AddApplicationInsightsTelemetryWorkerService();
            services.ConfigureFunctionsApplicationInsights();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
            });
        })
        .Build();
    
    host.Run();
    

    .csproj -

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <AzureFunctionsVersion>v4</AzureFunctionsVersion>
        <OutputType>Exe</OutputType>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <RootNamespace>_78600033</RootNamespace>
      </PropertyGroup>
      <ItemGroup>
        <FrameworkReference Include="Microsoft.AspNetCore.App" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.22.0" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.3.1" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.OpenApi" Version="2.0.0-preview2" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.3-preview2" />
        <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
        <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
      </ItemGroup>
      <ItemGroup>
        <None Update="host.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
        <None Update="local.settings.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
          <CopyToPublishDirectory>Never</CopyToPublishDirectory>
        </None>
      </ItemGroup>
      <ItemGroup>
        <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
      </ItemGroup>
    </Project>
    

    使用给定的代码,我能够成功访问Swagger UI页面。

    enter image description here

    enter image description here

    
    Azure Functions Core Tools
    Core Tools Version:       4.0.5801 Commit hash: N/A +5ac2f097******ea9ef68ff (64-bit)
    Function Runtime Version: 4.34.1.22669
    
    [2024-06-12T08:52:11.303Z] Found C:\Users\*****\repos\78600033\78600033\78600033.csproj. Using for user secrets file configuration.
    [2024-06-12T08:52:16.113Z] Azure Functions .NET Worker (PID: 15152) initialized in debug mode. Waiting for debugger to attach...
    [2024-06-12T08:52:16.157Z] Worker process started and initialized.
    
    Functions:
    
            GetConnector: [GET] http://localhost:7175/api/connectors/{id}
    
            RenderOAuth2Redirect: [GET] http://localhost:7175/api/oauth2-redirect.html
    
            RenderOpenApiDocument: [GET] http://localhost:7175/api/openapi/{version}.{extension}
    
            RenderSwaggerDocument: [GET] http://localhost:7175/api/swagger.{extension}
    
            RenderSwaggerUI: [GET] http://localhost:7175/api/swagger/ui
    
    For detailed output, run func with --verbose flag.
    [2024-06-12T08:52:21.270Z] Host lock lease acquired by instance ID '000000000000000000000000BF6D1ED5'.
    [2024-06-12T08:52:37.375Z] Executing 'Functions.RenderSwaggerUI' (Reason='This function was programmatically called via the host APIs.', Id=b43b9a2c-0021-4841-be41-0304378dd477)
    [2024-06-12T08:52:37.721Z] SwaggerUI page was requested.
    [2024-06-12T08:52:38.031Z] Executed 'Functions.RenderSwaggerUI' (Succeeded, Id=b43b9a2c-0021-4841-be41-0304378dd477, Duration=675ms)
    [2024-06-12T08:52:38.186Z] Executing 'Functions.RenderSwaggerDocument' (Reason='This function was programmatically called via the host APIs.', Id=a9dfe31d-d362-465c-a22c-4d201e46a185)
    [2024-06-12T08:52:38.216Z] swagger.json was requested.
    [2024-06-12T08:52:40.904Z] Executed 'Functions.RenderSwaggerDocument' (Succeeded, Id=a9dfe31d-d362-465c-a22c-4d201e46a185, Duration=2719ms)
    [2024-06-12T08:55:04.973Z] Executing 'Functions.GetConnector' (Reason='This function was programmatically called via the host APIs.', Id=bacfcce3-b5f1-431f-a4ae-5e132635ff18)
    [2024-06-12T08:55:05.025Z] Executed 'Functions.GetConnector' (Succeeded, Id=bacfcce3-b5f1-431f-a4ae-5e132635ff18, Duration=52ms)