当前位置:   article > 正文

SwaggerUI看烦了,IGeekFan.AspNetCore.Knife4jUI 帮你换个新皮肤

igeekfan.aspnetcore.knife4jui

背景

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

于是我一看,就说大话:“这个只是一套UI,他这个有开源地址么”

被@at说:你试试...

当天晚上就把swagger-ui, Knife4j,Swashbuckle.AspNetCore项目的源码都下载下来研究下,看看能不能集成到AspNETCore下,这样我们就能给Swagger UI换套新皮肤。

knife4j

knife4j 是swagger-bootstrap-ui库的升级版,作者已全面升级,全部以knife4j命名。

Gitee上也有2.8K

  • 效果图

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.AspNetCorespring-fox
封装成nuget包/maven包的UI库Swashbuckle.AspNetCore.SwaggerUIknife4j-v3-spring-ui
UI库swagger-ui-distknife4j-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)等等 。

  1. private readonly SwaggerUIOptions _options;
  2. //xxx
  3.   
  4. public async Task Invoke(HttpContext httpContext)
  5. //xxx
  6.     if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$"))
  7.     {
  8.         await RespondWithIndexHtml(httpContext.Response);
  9.         return;
  10.     }
  11. //xxx
  12.   }
  13. private async Task RespondWithIndexHtml(HttpResponse response)
  14. {
  15.     response.StatusCode = 200;
  16.     response.ContentType = "text/html;charset=utf-8";
  17.     using (var stream = _options.IndexStream())
  18.     {
  19.         // Inject arguments before writing to response
  20.         var htmlBuilder = new StringBuilder(new StreamReader(stream).ReadToEnd());
  21.         foreach (var entry in GetIndexArguments())
  22.         {
  23.             htmlBuilder.Replace(entry.Key, entry.Value);
  24.         }
  25.         await response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8);
  26.     }
  27. }
  28. private IDictionary<stringstring> GetIndexArguments()
  29. {
  30.     return new Dictionary<stringstring>()
  31.     {
  32.         { "%(DocumentTitle)", _options.DocumentTitle },
  33.         { "%(HeadContent)", _options.HeadContent },
  34.         { "%(ConfigObject)", JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions) },
  35.         { "%(OAuthConfigObject)", JsonSerializer.Serialize(_options.OAuthConfigObject, _jsonSerializerOptions) }
  36.     };
  37. }

在index.html中。

<title>%(DocumentTitle)</title>
  1. var configObject = JSON.parse('%(ConfigObject)');
  2. 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

  1. [
  2.     {
  3.         "name":"2.X版本",
  4.         "url":"/v2/api-docs?group=2.X版本",
  5.         "swaggerVersion":"2.0",
  6.         "location":"/v2/api-docs?group=2.X版本"
  7.     },
  8.     {
  9.         "name":"分组接口",
  10.         "url":"/v2/api-docs?group=分组接口",
  11.         "swaggerVersion":"2.0",
  12.         "location":"/v2/api-docs?group=分组接口"
  13.     },
  14.     {
  15.         "name":"默认接口",
  16.         "url":"/v2/api-docs?group=默认接口",
  17.         "swaggerVersion":"2.0",
  18.         "location":"/v2/api-docs?group=默认接口"
  19.     }
  20. ]
  • swagger 配置项

  • http://knife4j.xiaominfo.com/swagger-resources/configuration/ui 请求方法: GET

  1. {
  2.     "deepLinking":true,
  3.     "displayOperationId":false,
  4.     "defaultModelsExpandDepth":1,
  5.     "defaultModelExpandDepth":1,
  6.     "defaultModelRendering":"example",
  7.     "displayRequestDuration":false,
  8.     "docExpansion":"none",
  9.     "filter":false,
  10.     "operationsSorter":"alpha",
  11.     "showExtensions":false,
  12.     "tagsSorter":"alpha",
  13.     "validatorUrl":"",
  14.     "apisSorter":"alpha",
  15.     "jsonEditor":false,
  16.     "showRequestHeaders":false,
  17.     "supportedSubmitMethods":[
  18.         "get",
  19.         "put",
  20.         "post",
  21.         "delete",
  22.         "options",
  23.         "head",
  24.         "patch",
  25.         "trace"
  26.     ]
  27. }
  • 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 反向代理的地址(后台地址)

  1. proxy: {
  2.   "/": {
  3.     target: 'http://localhost:5000/',
  4.     ws: true,
  5.     changeOrigin: true
  6.   }
  7. }

安装依赖,并运行他

  1. yarn install
  2. 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串。

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

在swagger v3 版本中,/v3/api-docs/swagger-config,返回了分组信息,urls字段。

效果如下

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,

临时方案

  1. services.AddSwaggerGen(c =>
  2. {
  3.     c.AddServer(new OpenApiServer()
  4.     {
  5.         Url = "",
  6.         Description = "v1"
  7.     });
  8. });

但还有一个问题,前台根据operationId生成的路由,    [HttpPost(Name = "CreateProduct")]比如CreateProduct。有些没有设置 Name的,点击后就会出现空白界面。

增加CustomOperationIds的配置,通过反射获取方法名。

  1. services.AddSwaggerGen(c =>
  2. {
  3.     //xx
  4.      c.CustomOperationIds(apiDesc =>
  5.     {
  6.         return apiDesc.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null;
  7.     });
  8. });

解决了这些问题。

我们创建一个新类库,起名IGeekFan.AspNetCore.Knife4jUI

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

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

打包

yarn run build

复制到根目录,设置为嵌入文件,删除不需要的images和txt文本。

  1. <ItemGroup>
  2.  <EmbeddedResource Include="knife4j/**/*" />
  3.  <EmbeddedResource Include="favicon.ico" />
  4.  <EmbeddedResource Include="index.html" />
  5. </ItemGroup>

将后台Swashbuckle.AspNetCore.SwaggerUI的代码复制过来,全部重命名。比如中间件名字为

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

Knife4jUIMiddleware修改位置

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

删除无用的替换变量,增加

Knife4UIOptions 修改

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

Startup 中的Configure中间件

将UseSwaggerUI()改成UseKnife4UI()

  1. app.UseKnife4UI(c =>
  2. {
  3.     c.RoutePrefix = ""// serve the UI at root
  4.     c.SwaggerEndpoint("/v1/api-docs""V1 Docs");
  5.     c.SwaggerEndpoint("/gp/api-docs""登录模块");
  6. });

不用IGeekFan.AspNetCore.Knife4jUI也能实现?

当然,可以。

我们也能通过其他方式,在SwaggerUI的基础上,替换比如替换Index.html页面,自己打包前端UI,复制到项目中等。

将knife4j-vue-v3项目打包,放到wwwwroot目录中。

需要配置静态文件。

    app.UseStaticFiles();
  1. app.UseSwaggerUI(c =>
  2. {
  3.         c.RoutePrefix = ""// serve the UI at root
  4.         c.SwaggerEndpoint("/v1/api-docs""V1 Docs");//这个配置无效。
  5.         c.IndexStream = () => new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")).GetFileInfo("index.html").CreateReadStream();
  6. });

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

  1. app.UseEndpoints(endpoints =>
  2. {
  3.     endpoints.MapControllers();
  4.     endpoints.MapSwagger("{documentName}/api-docs");
  5.     endpoints.MapGet("/v3/api-docs/swagger-config", async (httpContext) =>
  6.     {
  7.         JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions();
  8.         _jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
  9.         _jsonSerializerOptions.IgnoreNullValues = true;
  10.         _jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, false));
  11.         SwaggerUIOptions _options = new SwaggerUIOptions()
  12.         {
  13.             ConfigObject = new ConfigObject()
  14.             {
  15.                 Urls = new List<UrlDescriptor>
  16.                 {
  17.                     new UrlDescriptor()
  18.                     {
  19.                         Url="/v1/api-docs",
  20.                         Name="V1 Docs"
  21.                     }
  22.                 }
  23.             }
  24.         };
  25.         await httpContext.Response.WriteAsync(JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions));
  26.     });
  27. });

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.

  1. Package Manager : Install-Package IGeekFan.AspNetCore.Knife4jUI
  2. 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.

  1. using System.Reflection;
  2. using Microsoft.OpenApi.Models;
  3. using Swashbuckle.AspNetCore.SwaggerGen;
  4. using IGeekFan.AspNetCore.Knife4jUI;

???? ConfigureServices

3.服务配置,CustomOperationIds和AddServer是必须的。

  1.    services.AddSwaggerGen(c =>
  2.     {
  3.         c.SwaggerDoc("v1",new OpenApiInfo{Title = "API V1",Version = "v1"});
  4.         c.AddServer(new OpenApiServer()
  5.         {
  6.             Url = "",
  7.             Description = "vvv"
  8.         });
  9.         c.CustomOperationIds(apiDesc =>
  10.         {
  11.             return apiDesc.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null;
  12.         });
  13.     });

???? Configure

  1. 中间件配置

  1. app.UseSwagger();
  2. app.UseKnife4UI(c =>
  3. {
  4.     c.RoutePrefix = ""// serve the UI at root
  5.     c.SwaggerEndpoint("/v1/api-docs""V1 Docs");
  6. });
  7. app.UseEndpoints(endpoints =>
  8. {
  9.     endpoints.MapControllers();
  10.     endpoints.MapSwagger("{documentName}/api-docs");
  11. });

???? 效果图

运行项目,打开 https://localhost:5001/index.html#/home

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/93439?site
推荐阅读
相关标签
  

闽ICP备14008679号