天天看點

SwaggerUI看煩了,IGeekFan.AspNetCore.Knife4jUI 幫你換個新皮膚

背景

好像是上周四,看到微信群有人說java有輪子swagger-bootstrap-ui,而c#,就是找不到。

于是我一看,就說大話:“這個隻是一套UI,他這個有開源位址麼”

被@at說:你試試...

當天晚上就把swagger-ui, Knife4j,Swashbuckle.AspNetCore項目的源碼都下載下傳下來研究下,看看能不能內建到AspNETCore下,這樣我們就能給Swagger UI換套新皮膚。

knife4j

SwaggerUI看煩了,IGeekFan.AspNetCore.Knife4jUI 幫你換個新皮膚

knife4j 是swagger-bootstrap-ui庫的更新版,作者已全面更新,全部以knife4j命名。

Gitee上也有2.8K

  • 效果圖
    SwaggerUI看煩了,IGeekFan.AspNetCore.Knife4jUI 幫你換個新皮膚

IGeekFan.AspNetCore.Knife4jUI

他是swagger ui 庫:knife4j UI 的.NET Core封裝,支援 .NET Core3.0+或.NET Standard2.0。

  • https://github.com/luoyunchong/IGeekFan.AspNetCore.Knife4jUI

概念對應關系如下

功能 c# java
實作swagger規範 Swashbuckle.AspNetCore spring-fox
封裝成nuget包/maven包的UI庫 Swashbuckle.AspNetCore.SwaggerUI knife4j-v3-spring-ui
UI庫 swagger-ui-dist knife4j-vue-v3(swagger v3版本)

注意

swagger v2和v3版本不一樣,我隻實作了swagger v3版本的封裝。

源碼下載下傳

  • https://gitee.com/xiaoym/knife4j
  • https://github.com/domaindrivendev/Swashbuckle.AspNetCore

Swashbuckle.AspNetCore.SwaggerUI源碼分析。

通過中間件SwaggerUI中間件Middleware,Invoke方法中,替換了Index.html中的%(DocumentTitle) %(HeadContent),%(ConfigObject)等等 。

private readonly SwaggerUIOptions _options;
//xxx
  
public async Task Invoke(HttpContext httpContext)
{ 
//xxx
    if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$"))
    {
        await RespondWithIndexHtml(httpContext.Response);
        return;
    }
//xxx
  }

private async Task RespondWithIndexHtml(HttpResponse response)
{
    response.StatusCode = 200;
    response.ContentType = "text/html;charset=utf-8";

    using (var stream = _options.IndexStream())
    {
        // Inject arguments before writing to response
        var htmlBuilder = new StringBuilder(new StreamReader(stream).ReadToEnd());
        foreach (var entry in GetIndexArguments())
        {
            htmlBuilder.Replace(entry.Key, entry.Value);
        }

        await response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8);
    }
}

private IDictionary<string, string> GetIndexArguments()
{
    return new Dictionary<string, string>()
    {
        { "%(DocumentTitle)", _options.DocumentTitle },
        { "%(HeadContent)", _options.HeadContent },
        { "%(ConfigObject)", JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions) },
        { "%(OAuthConfigObject)", JsonSerializer.Serialize(_options.OAuthConfigObject, _jsonSerializerOptions) }
    };
}
           

在index.html中。

<title>%(DocumentTitle)</title>
           
var configObject = JSON.parse('%(ConfigObject)');
var oauthConfigObject = JSON.parse('%(OAuthConfigObject)');
           

當我們寫的aspnetcore項目內建swagger元件後,隻會有一個ajax的異步請求

knife4j-v3-spring-ui

效果(2.X版):http://knife4j.xiaominfo.com/doc.html

由于官方也沒有v3的demo,我們可以暫時通過v2分析,發現他有3個異步請求,有一個請求傳回相似的。另一個則是swagger的配置項,可以發現,傳回值與SwaggerUIOptions一緻。

功能 c# (swagger v3) java(swagger v2)
擷取分組配置 /swagger-resources
swagger配置項 /swagger-resources/configuration/ui
api文檔 https://api.igeekfan.cn/swagger/v1/swagger.json /v2/api-docs?group=2.X版本

結構如下。

  • 版本分組配置
  • http://knife4j.xiaominfo.com/swagger-resources
[
    {
        "name":"2.X版本",
        "url":"/v2/api-docs?group=2.X版本",
        "swaggerVersion":"2.0",
        "location":"/v2/api-docs?group=2.X版本"
    },
    {
        "name":"分組接口",
        "url":"/v2/api-docs?group=分組接口",
        "swaggerVersion":"2.0",
        "location":"/v2/api-docs?group=分組接口"
    },
    {
        "name":"預設接口",
        "url":"/v2/api-docs?group=預設接口",
        "swaggerVersion":"2.0",
        "location":"/v2/api-docs?group=預設接口"
    }
]
           
  • swagger 配置項
  • http://knife4j.xiaominfo.com/swagger-resources/configuration/ui 請求方法: GET
{
    "deepLinking":true,
    "displayOperationId":false,
    "defaultModelsExpandDepth":1,
    "defaultModelExpandDepth":1,
    "defaultModelRendering":"example",
    "displayRequestDuration":false,
    "docExpansion":"none",
    "filter":false,
    "operationsSorter":"alpha",
    "showExtensions":false,
    "tagsSorter":"alpha",
    "validatorUrl":"",
    "apisSorter":"alpha",
    "jsonEditor":false,
    "showRequestHeaders":false,
    "supportedSubmitMethods":[
        "get",
        "put",
        "post",
        "delete",
        "options",
        "head",
        "patch",
        "trace"
    ]
}
           
  • api 文檔
  • http://knife4j.xiaominfo.com/v2/api-docs?group=2.X%E7%89%88%E6%9C%AC

接下來我們看下knife4j,可以看到,他有knife4j-vue-v3項目,這個是swagger v3版本的vue實作。

我們打開knife4j-vue-v3項目,修改配置項vue.config.js,devServer 反向代理的位址(背景位址)

proxy: {
  "/": {
    target: 'http://localhost:5000/',
    ws: true,
    changeOrigin: true
  }
}
           

安裝依賴,并運作他

yarn install
yarn serve
           

我們會看到一個請求錯誤。Knife4j文檔請求異常,因為背景并沒有:'/v3/api-docs/swagger-config'。

也就是上文中的/swagger-resources/configuration/ui,我們可以在SwaggerUIMiddleware中間件擷取這些參數,原本是通過替換字元串,現在,我們可以寫一個api。怎麼寫呢。

下載下傳Swashbuckle.AspNetCore的源碼,打開Swashbuckle.AspNetCore.sln。

我們嘗試修改Swashbuckle.AspNetCore.SwaggerUI項目中,SwaggerUIMiddleware中的源碼。

Invoke方法增加如下處理,将配置項直接傳回json串。

if (httpMethod == "GET" && Regex.IsMatch(path, $"^/v3/api-docs/swagger-config$"))
{
     await httpContext.Response.WriteAsync(JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions));
    return;
}

           

在swagger v3 版本中,/v3/api-docs/swagger-config,傳回了分組資訊,urls字段。 

效果如下

SwaggerUI看煩了,IGeekFan.AspNetCore.Knife4jUI 幫你換個新皮膚

image

設定test/WebSites/Basic項目為啟動項目,運作後,打開了http://localhost:5000/index.html,這個還是原本的swagger ui,我們打開http://localhost:8080/#/home,前台依舊提示有問題。

AddSwaggerGen 需要增加Server,前台判斷有BUG,非空。

image

servers.length得到的是0,問号表達式就會執行後面的servers[0].url,

SwaggerUI看煩了,IGeekFan.AspNetCore.Knife4jUI 幫你換個新皮膚

臨時方案

services.AddSwaggerGen(c =>
{

    c.AddServer(new OpenApiServer()
    {
        Url = "",
        Description = "v1"
    });
});
           

但還有一個問題,前台根據operationId生成的路由,    [HttpPost(Name = "CreateProduct")]比如CreateProduct。有些沒有設定 Name的,點選後就會出現空白界面。

增加CustomOperationIds的配置,通過反射擷取方法名。

services.AddSwaggerGen(c =>
{
    //xx
     c.CustomOperationIds(apiDesc =>
    {
        return apiDesc.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null;
    });
});
           

解決了這些問題。

我們建立一個新類庫,起名IGeekFan.AspNetCore.Knife4jUI

将前端打包。修改打封包件配置,vue.config.js

assetsDir: "knife4j",
indexPath: "index.html"
           

打包

yarn run build
           

複制到根目錄,設定為嵌入檔案,删除不需要的images和txt文本。

<ItemGroup>
 <EmbeddedResource Include="knife4j/**/*" />
 <EmbeddedResource Include="favicon.ico" />
 <EmbeddedResource Include="index.html" />
</ItemGroup>
           

将背景Swashbuckle.AspNetCore.SwaggerUI的代碼複制過來,全部重命名。比如中間件名字為

SwaggerUIMiddleware -> Knife4jUIMiddleware。即SwaggerUI都改成Knife4jUI。

Knife4jUIMiddleware修改位置

private const string EmbeddedFileNamespace = "IGeekFan.AspNetCore.Knife4jUI";
           

删除無用的替換變量,增加

Knife4UIOptions 修改

public Func<Stream> IndexStream { get; set; } = () => typeof(Knife4UIOptions).GetTypeInfo().Assembly
            .GetManifestResourceStream("IGeekFan.AspNetCore.Knife4jUI.index.html");
           

Startup 中的Configure中間件

将UseSwaggerUI()改成UseKnife4UI()

app.UseKnife4UI(c =>
{
    c.RoutePrefix = ""; // serve the UI at root
    c.SwaggerEndpoint("/v1/api-docs", "V1 Docs");
    c.SwaggerEndpoint("/gp/api-docs", "登入子產品");
});
           

不用IGeekFan.AspNetCore.Knife4jUI也能實作?

當然,可以。

我們也能通過其他方式,在SwaggerUI的基礎上,替換比如替換Index.html頁面,自己打包前端UI,複制到項目中等。

将knife4j-vue-v3項目打包,放到wwwwroot目錄中。

需要配置靜态檔案。

    app.UseStaticFiles();
           
app.UseSwaggerUI(c =>
{
        c.RoutePrefix = ""; // serve the UI at root
        c.SwaggerEndpoint("/v1/api-docs", "V1 Docs");//這個配置無效。
        c.IndexStream = () => new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")).GetFileInfo("index.html").CreateReadStream();
});
           

重寫/v3/api-docs/swagger-config路由

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapSwagger("{documentName}/api-docs");
    endpoints.MapGet("/v3/api-docs/swagger-config", async (httpContext) =>
    {

        JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions();
        _jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        _jsonSerializerOptions.IgnoreNullValues = true;
        _jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, false));

        SwaggerUIOptions _options = new SwaggerUIOptions()
        {
            ConfigObject = new ConfigObject()
            {
                Urls = new List<UrlDescriptor>
                {
                    new UrlDescriptor()
                    {
                        Url="/v1/api-docs",
                        Name="V1 Docs"
                    }
                }
            }
        };

        await httpContext.Response.WriteAsync(JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions));
    });
});
           

IGeekFan.AspNetCore.Knife4jUI指南

相關依賴項

knife4j

  • knife4j-vue-v3(不是vue3,而是swagger-ui-v3版本)

Swashbuckle.AspNetCore

  • Swashbuckle.AspNetCore.Swagger
  • Swashbuckle.AspNetCore.SwaggerGen

Demo

  • Basic
  • Knife4jUIDemo

📚 快速開始

🚀安裝包

1.Install the standard Nuget package into your ASP.NET Core application.

Package Manager : Install-Package IGeekFan.AspNetCore.Knife4jUI
CLI : dotnet add package IGeekFan.AspNetCore.Knife4jUI
           

2.In the ConfigureServices method of Startup.cs, register the Swagger generator, defining one or more Swagger documents.

using System.Reflection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using IGeekFan.AspNetCore.Knife4jUI;
           

🚁 ConfigureServices

3.服務配置,CustomOperationIds和AddServer是必須的。

   services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1",new OpenApiInfo{Title = "API V1",Version = "v1"});
        c.AddServer(new OpenApiServer()
        {
            Url = "",
            Description = "vvv"
        });
        c.CustomOperationIds(apiDesc =>
        {
            return apiDesc.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null;
        });
    });
           

💪 Configure

  1. 中間件配置
app.UseSwagger();

app.UseKnife4UI(c =>
{
    c.RoutePrefix = ""; // serve the UI at root
    c.SwaggerEndpoint("/v1/api-docs", "V1 Docs");
});

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapSwagger("{documentName}/api-docs");
});
           

🔎 效果圖

運作項目,打開 https://localhost:5001/index.html#/home

SwaggerUI看煩了,IGeekFan.AspNetCore.Knife4jUI 幫你換個新皮膚

https://pic.downk.cc/item/5f2fa77b14195aa594ccbedc.jpg

更多配置請參考

  • https://github.com/domaindrivendev/Swashbuckle.AspNetCore

更多項目

  • https://api.igeekfan.cn/swagger/index.html
  • https://github.com/luoyunchong/lin-cms-dotnetcore

繼續閱讀