65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Swagger UI 进行文档和测试 API

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (5投票s)

2018年12月16日

CPOL

6分钟阅读

viewsIcon

21152

开发人员在测试 API 时,经常通过浏览器请求或使用 POSTMAN、Advanced Rest Client (ARC) 等客户端进行测试。

引言

开发人员在测试 API 时,经常通过浏览器请求或使用 POSTMAN、Advanced Rest Client (ARC) 等客户端进行测试。为了展示 API 的功能,我们通常还需要通过一些需要额外工作的手段来暴露方法、描述以及相关数据结构。为了补充其中一个或两个功能,Swagger 会派上用场,它可以通过配置提供 API 文档和 API 测试。

Swagger 是一组围绕 OpenAPI 规范构建的开源工具,可用于自动生成 API 文档。Swagger UI 是一个可以在 API 生命周期中使用的工具。Swagger 提供易于导航的 API 资源文档和/或可视化,并允许直接在应用程序内部与 API 进行交互,从而使开发和测试工作以及最终用户体验无缝顺畅。在本文中,我将讨论如何在 API 中实现 Swagger,并举例说明 Swagger UI 的一些用例。我将使用 .NET Core 2.0 Web API 应用程序和 Visual Studio 2017 IDE。我创建了一个具有单个控制器和四个方法的示例 API 应用程序作为演示 可供下载

Swagger 提供了最强大、最易于使用的工具来充分利用 OpenAPI 规范。

配置

将 Swagger 集成到应用程序中相当简单,只需四个简单步骤即可完成,即安装、导入、注册和端点启用。

可以通过程序包管理器控制台安装该程序包,或者通过“NuGet 程序包管理器”菜单进行安装。

Install-Package Swashbuckle.AspNetCore

图 1:在程序包管理器控制台中安装 Swagger。

安装完成后,必须将 Swagger 导入到 Startup.cs 文件中。

using Swashbuckle.AspNetCore.Swagger;

然后,必须在 Startup.cs 文件的 ConfigureServices 方法中注册 Swagger 作为服务。

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc(_version, new Info { Title = _applicationName, Version =   _version });
});

最后,应用程序必须在 Startup.cs 文件的 Configure 方法中启用 Swagger 的 JSON 和 UI 端点,以便最终用户可以通过 Swagger UI 与 API 方法进行交互。

// Enable Swagger JSON endpoint.
app.UseSwagger(); 
// Enable swagger-ui (HTML, JS, CSS, etc.)
app.UseSwaggerUI(c =>
{
     c.SwaggerEndpoint($"/swagger/{_version}/swagger.json",${_applicationName} {_version}"); 
});

Startup.cs 文件的完整列表显示在代码片段 1 中。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;

namespace Api
{
    public class Startup
    {
        private string _applicationName;
        private string _version;
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
        
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            var config = Configuration.GetSection("ApplicationSetting").Get<ApplicationSetting>();
            _applicationName = config.Name;
            _version = config.Version;

          // Register the Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc(_version, new Info { Title = _applicationName, Version = _version });
                // Set the comments path for the Swagger JSON and UI.
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                c.IncludeXmlComments(xmlPath);
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
           
            // Enable Swagger JSON endpoint.
            app.UseSwagger();
            // Enable swagger-ui (HTML, JS, CSS, etc.), with the Swagger JSON endpoint.
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint($"/swagger/{_version}/swagger.json", $"{_applicationName} {_version}");
            });
            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}
代码片段 1:Startup.cs 的内容

安全

API 是已安全保护的,因此对 Swagger UI 及其端点进行安全保护,以防止未经授权的用户通过 Swagger UI 查看 API 文档,这是很有意义的。在这种情况下,必须使用常规的身份验证机制。例如,可以使用基于 WS-Federation 的身份验证,通过单点登录页面重定向用户进行身份验证。假设已放置了类似 WS-Federation 的身份验证,则 Configure 方法必须在 'app.UserSwagger();' 代码行之前包含以下代码。

//Enable authentication
app.UseAuthentication();
app.Use(async (context, next) =>
   {
       if (!context.User.Identity.IsAuthenticated && context.Request.Path != "/signin-wsfed")
        {
           await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
        }
        else
        {
           await next();
         }
     });
    
// Enable Swagger JSON endpoint.
app.UseSwagger();

// Enable swagger-ui (HTML, JS, CSS, etc.), with the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
   {
      c.SwaggerEndpoint($"/swagger/{_version}/swagger.json", $"{_applicationName} {_version}");
   }); 

此外,ConfigureServices 方法必须包含以下代码,以触发与 WsFederation 相关的身份验证中间件。

//Authentication
services.AddAuthentication(options =>
{
   options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
   options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
   options.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddWsFederation(options =>
{
   options.Wtrealm = "ABC"; //use the site name
  options.MetadataAddress = "XYZ"; //use the server that does the SSO
});

在身份验证到位后,对 Swagger UI 或 Swagger JSON 端点的调用必须经过身份验证中间件,因此 Swagger UI 仅限于合法用户。

可视化

完成上述四个步骤后,Swagger 即可使用。通过 https://:5001/swagger/v1/swagger.json 浏览即可在浏览器或 POSTMAN、Advanced Rest Client (ARC) 等客户端中以 JSON 格式获取数据。返回的 JSON 对象提供了 REST 方法(按控制器分隔)以及 API 使用的对象规范。响应示例为代码片段 2。

{
    "swagger": "2.0",
    "info": {
        "version": "v1",
        "title": "SmartStock API"
    },
    "paths": {
        "/api/Stock": {
            "get": {
                "tags": [
                    "Stock"
                ],
                "operationId": "GetStock",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "uniqueItems": false,
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Stock"
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "Stock"
                ],
                "operationId": "PostStock",
                "consumes": [
                    "application/json-patch+json",
                    "application/json",
                    "text/json",
                    "application/*+json"
                ],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [
                    {
                        "name": "stock",
                        "in": "body",
                        "required": false,
                        "schema": {
                            "$ref": "#/definitions/Stock"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "$ref": "#/definitions/Stock"
                        }
                    }
                }
            }
        },
        "/api/Stock/{symbol}": {
            "get": {
                "tags": [
                    "Stock"
                ],
                "operationId": "GetStock",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [
                    {
                        "name": "symbol",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "uniqueItems": false,
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Stock"
                            }
                        }
                    }
                }
            }
        },
        "/api/Stock/{id}": {
            "delete": {
                "tags": [
                    "Stock"
                ],
                "operationId": "DeleteStock",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "string",
                        "format": "uuid"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "type": "boolean"
                        }
                    }
                }
            }
        },
        "/api/Values": {
            "get": {
                "tags": [
                    "Values"
                ],
                "operationId": "Get",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "uniqueItems": false,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    }
                },
                "deprecated": true
            },
            "post": {
                "tags": [
                    "Values"
                ],
                "operationId": "Post",
                "consumes": [
                    "application/json-patch+json",
                    "application/json",
                    "text/json",
                    "application/*+json"
                ],
                "produces": [],
                "parameters": [
                    {
                        "name": "value",
                        "in": "body",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "deprecated": true
            }
        },
        "/api/Values/{id}": {
            "get": {
                "tags": [
                    "Values"
                ],
                "operationId": "Get",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "type": "string"
                        }
                    }
                },
                "deprecated": true
            },
            "put": {
                "tags": [
                    "Values"
                ],
                "operationId": "Put",
                "consumes": [
                    "application/json-patch+json",
                    "application/json",
                    "text/json",
                    "application/*+json"
                ],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    },
                    {
                        "name": "value",
                        "in": "body",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "deprecated": true
            },
            "delete": {
                "tags": [
                    "Values"
                ],
                "operationId": "Delete",
                "consumes": [],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "deprecated": true
            }
        }
    },
    "definitions": {
        "Stock": {
            "type": "object",
            "properties": {
                "id": {
                    "format": "uuid",
                    "type": "string"
                },
                "ticker": {
                    "type": "string"
                },
                "company": {
                    "type": "string"
                },
                "price": {
                    "format": "double",
                    "type": "number"
                }
            }
        }
    }
}
代码片段 2:JSON 响应示例

可以通过导航到 https://:5001/swagger/index.html 来访问 Swagger UI,用户可以在其中以交互式格式可视化 JSON 响应中提供的数据。此 UI 还支持实际执行 REST 方法。

图 2:在 Swagger UI 中可视化 API

测试

Swagger 提供了无需任何工具即可测试 API 的功能。例如,单击 GET(图 2 中的第一个选项卡)会展开该方法。通过单击“Try it Out”(试用),然后单击“Execute”(执行),Swagger 会触发对 /api/stock 中 'get' 方法的调用。请注意,演示应用程序中有一个名为 'StockController' 的控制器。结果显示在 UI 中,也可以下载。图 3 显示了 get 方法的结果屏幕。UI 中暴露的任何方法都可以在 UI 本身中执行,从而使我们能够直接从 API 本身测试 API。

图 3:在 Swagger UI 中测试 API 方法

对属性的支持

路由属性、Http* 属性以及任何其他数据注解都得到默认支持。图 4 显示了用 HttpGetHttpPostHttpDelete 装饰的方法如何在 Swagger UI 中显示。

图 4:Swagger UI 反映 HTTP 属性

Swagger 与 .NET 中的属性同步。例如,如果一个控制器类用 [Obsolete] 属性进行装饰,UI 将反映该控制器是“过时的”。图 5 显示 ValuesController 被标记为“Obsolete”,并且 Swagger UI 反映了这一点,因此无法单击。当 API 在不破坏现有代码的情况下淘汰某些功能时,此功能非常有用。

图 5:Swagger UI 反映 Obsolete 属性

支持 XML 文档

通过一些配置更改,Swagger 支持 UI 中的文档。通过将以下代码行添加到 *.csproj 文件中,即可生成 XML 文档。

<Property Group>
     ....
     <GenerateDocumentationFile>
          true
     </GenerateDocumentationFile> 
     <NoWarn>
          $(NoWarn);1591
     </NoWarn>
<Property Group>
通过在 Startup.cs 中使用以下代码行,在注册时,UI 将显示 XML 文档。
services.AddSwaggerGen(c => 
      {   
          ...
            var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); 
            c.IncludeXmlComments(xmlPath);
}
);

通过使用 XML 路径的更改,Swagger 在 UI 中公开了 XML 文档。请注意,可以通过 XML 注释来为代码添加文档,这些注释直接放在代码块之前。例如,以下代码块显示了 GetStock() 方法的 XML 注释,该注释在 Swagger UI 中查看时,会为该方法简单显示“此方法返回股票列表”的描述,如图 6 所示。

    <summary>/// This method returns a list of stocks /// </summary>
    [HttpGet] public IEnumerable<stock> GetStock()
    { 
        return Data.DbContext.GetStocks(); 
    } 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

图 6:Swagger UI 显示文档。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Swagger 支持所有主流浏览器,并且可以在本地或 Web 环境中运行。UI 的外观和感觉是可自定义的。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

我之前展示了它在本地计算机上的工作方式。Swagger UI 在云中也能很好地工作。部署到 Azure 的同一应用程序 https://smartstockapi.azurewebsites.net/swagger/index.html 的运行方式与在我的本地计算机上一样。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

图 7:托管在云端(Azure)的应用程序中的 Swagger UI

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

资源/材料/参考

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

© . All rights reserved.