背景

  尽管.net6已经发布很久了,但是公司的项目由于种种原因依旧基于.net Framework。伴随着版本迭代,后端的api接口不断增多,每次在联调的时候,前端开发叫苦不迭:“小胖,你们的swagger页面越来越卡了,快优化优化!”。

先查看swagger页面加载耗时:



  以上分别是:

  • v1加载了两次
  • 重新编译程序后打开swagger页面,加载v1(api json)竟然耗时两分多钟。
  • 第一次完整加载页面后重新刷新页面,再次查看swagger的耗时,这次明显页面加载速度提升了不少,但依旧不尽人人意,json返回后渲染耗时太久。

探察&解决

  swagger加载的卡慢问题,萌生了优化swagger的想法,刚开始按传统技能在网络上搜索了一大圈依旧未找到解决方案。幸好swashbuckle开源,还能自己动手分析了。先下载好源码GitHub - domaindrivendev/Swashbuckle.WebApi: Seamlessly adds a swagger to WebApi projects!

一、先看看v1加载慢,却要加载两次

  从上面的图上不难发现第二次v1的加载是跟在lang.js后面,而lang.js实际上就是用来做汉化。打开项目中这个文件

  原来是为了添加控制器注释,重新访问后端取一次接口文档。在查看了源码js后,得到一个更简单的方式,页面的汉化翻译,是在数据取完页面已经渲染后才进行的,可直接使用window.swaggerApi.swaggerObject.ControllerDesc。

  1. setControllerSummary: function () {
  2. var summaryDict = window.swaggerApi.swaggerObject.ControllerDesc;
  3. var id, controllerName, strSummary;
  4. $("#resources_container .resource").each(function (i, item) {
  5. id = $(item).attr("id");
  6. if (id) {
  7. controllerName = id.substring(9);
  8. try {
  9. strSummary = summaryDict[controllerName];
  10. if (strSummary) {
  11. $(item).children(".heading").children(".options").first().prepend('<li class="controller-summary" style="color:green;" title="' + strSummary + '">' + strSummary + '</li>');
  12. }
  13. } catch (e) {
  14. console.log(e);
  15. }
  16. }
  17. });
  18. },

  修改完文件以后,再看看页面的加载,已经不会重复去访问v1。

二、接下来处理v1加载慢

  先看看项目的的swagger配置:

  1. GlobalConfiguration.Configuration
  2. .EnableSwagger(c =>
  3. {
  4. c.IncludeXmlComments(GetXmlCommentsPath(thisAssembly.GetName().Name));
  5. c.IncludeXmlComments(GetXmlCommentsPath("xxxx.Api.Dto"));
  6. c.SingleApiVersion("v1", "xxxx.Api");
  7. c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider));
  8. })

  配置不多,其中有个CachingSwaggerProvider,实现了GetSwagger方法自定义返回数据,在这个方法里可以得知,实际上对api文档是有做缓存处理,v1加载的数据也就是这个SwaggerDocument。这也意味着,v1加载慢的原因出在这里。

  1. public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
  2. {
  3. var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion);
  4. SwaggerDocument srcDoc = null;
  5. //只读取一次
  6. if (!_cache.TryGetValue(cacheKey, out srcDoc))
  7. {
  8. srcDoc = (_swaggerProvider as Swashbuckle.Swagger.SwaggerGenerator).GetSwagger(rootUrl, apiVersion);
  9. srcDoc.vendorExtensions = new Dictionary<string, object> { { "ControllerDesc", GetControllerDesc() } };
  10. _cache.TryAdd(cacheKey, srcDoc);
  11. }
  12. return srcDoc;
  13. }

  调试程序的时候,swashbuckle提供的GetSwagger方法占据了大量的耗时。将源码Swashbuckle.Core引用进来,重新打开swagger时会有个小问题,资源文件都报404错误,这个是因为嵌入资源文件没有找到

  1. <ItemGroup>
  2. <EmbeddedResource Include="..\swagger-ui\dist\**\*.*">
  3. <LogicalName>%(RecursiveDir)%(FileName)%(Extension)</LogicalName>
  4. <InProject>false</InProject>
  5. </EmbeddedResource>
  6. </ItemGroup>

  根据路径查看,swagger-ui下是空白的。将从其他地方找到的或者从反编译文件里整理出来的文件放到该目录下,并将swagger-ui作为依赖项,重新编译项目后swagger页面加载资源文件就正常了。(如果有遇到依旧找不到资源文件的情况,重新再添加一次依赖项编译项目即可)

  接下来就可以开始调试了,经过一番波折,最终将元凶定位到了SwaggerGenerator中GetSwagger方法里获取paths这个地方,实际上就是在使用CreatePathItem的时候耗时过久

  1. var paths = GetApiDescriptionsFor(apiVersion)
  2. .Where(apiDesc => !(_options.IgnoreObsoleteActions && apiDesc.IsObsolete()))
  3. .OrderBy(_options.GroupingKeySelector, _options.GroupingKeyComparer)
  4. .GroupBy(apiDesc => apiDesc.RelativePathSansQueryString())
  5. .ToDictionary(group => "/" + group.Key, group => CreatePathItem(group, schemaRegistry));

  刚开始尝试用多线程的方式进行处理,尽管确实能够缩短获取json数据的时间,但依旧有两个问题:

  • 线程不安全,时不时页面会报错
  • 即使能快速返回json数据,页面渲染耗慢的问题依旧未解决。正如前面我们的项目中GetSwagger是使用到缓存的,在重新刷新swagger时,依旧存在卡慢问题。

三、将需返回json数据

  优化swagger加载,需要同时考虑到前端渲染页面以及后端梳理json数据所导致的页面加载慢问题。有什么好的办法么?swashbuckle core版本是支持分组的,但是项目使用的Framework版本不支持,既然不支持,就直接改造源码,按控制器分组,说干就干:

  找到HttpConfigurationExtensions类的EnableSwagger方法,这个方法用来配置路由

  1. public static SwaggerEnabledConfiguration EnableSwagger(
  2. this HttpConfiguration httpConfig,
  3. string routeTemplate,
  4. Action<SwaggerDocsConfig> configure = null)
  5. {
  6. var config = new SwaggerDocsConfig();
  7. if (configure != null) configure(config);
  8. httpConfig.Routes.MapHttpRoute(
  9. name: "swagger_docs" + routeTemplate,
  10. routeTemplate: routeTemplate,
  11. defaults: null,
  12. constraints: new { apiVersion = @".+" },
  13. handler: new SwaggerDocsHandler(config)
  14. );
  15. //配置控制器路由
  16. string controllRouteTemplate=DefaultRouteTemplate+"/{controller}";
  17. httpConfig.Routes.MapHttpRoute(
  18. name: "swagger_docs" + controllRouteTemplate,
  19. routeTemplate: controllRouteTemplate,
  20. defaults: null,
  21. constraints: new { apiVersion = @".+" },
  22. handler: new SwaggerDocsHandler(config)
  23. );
  24. return new SwaggerEnabledConfiguration(
  25. httpConfig,
  26. config.GetRootUrl,
  27. config.GetApiVersions().Select(version => routeTemplate.Replace("{apiVersion}", version)));
  28. }

  接下来找到SwaggerDocsHandler类,修改SendAsync方法,获取controller,并将controller传递到GetSwagger中

  1. protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  2. {
  3. var swaggerProvider = _config.GetSwaggerProvider(request);
  4. var rootUrl = _config.GetRootUrl(request);
  5. var apiVersion = request.GetRouteData().Values["apiVersion"].ToString();
  6. var controller = request.GetRouteData().Values["controller"]?.ToString();
  7. if (string.IsNullOrEmpty(controller))
  8. {
  9. controller = "Account";
  10. }
  11. try
  12. {
  13. var swaggerDoc = swaggerProvider.GetSwagger(rootUrl, apiVersion, controller);
  14. var content = ContentFor(request, swaggerDoc);
  15. return TaskFor(new HttpResponseMessage { Content = content });
  16. }
  17. catch (UnknownApiVersion ex)
  18. {
  19. return TaskFor(request.CreateErrorResponse(HttpStatusCode.NotFound, ex));
  20. }
  21. }

  相对应的修改ISwagger接口,以及接口的实现类SwaggerGenerator,增加按Controller筛选

  1. public interface ISwaggerProvider
  2. {
  3. SwaggerDocument GetSwagger(string rootUrl, string apiVersion,string controller);
  4. }
  1. SwaggerGeneratorGetSwagger修改:
  1. var temps = GetApiDescriptionsFor(apiVersion)
  2. .Where(apiDesc => !(_options.IgnoreObsoleteActions && apiDesc.IsObsolete()));
  3. if (string.IsNullOrEmpty(controller) == false)
  4. {
  5. temps = temps.Where(apiDesc => apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower() == controller.ToLower());
  6. }
  7. var paths = temps
  8. .OrderBy(_options.GroupingKeySelector, _options.GroupingKeyComparer)
  9. .GroupBy(apiDesc => apiDesc.RelativePathSansQueryString())
  10. .ToDictionary(group => "/" + group.Key, group => CreatePathItem(group, schemaRegistry));

  自己项目中关于ISwagger实现也要修改,然后开始重新编译自己的项目,重新打开swagger页面,页面在后端编译后第一次打开也非常迅速。默认打开的是Account控制器下的接口,如果切换到其他控制器下的接口只需要在url后加入对应的/Controller

四、修改Swagger页面

  以上我们已经把页面的加载慢的问题解决了,但在切换控制器上是否过于麻烦,能不能提升前端开发人员的使用体验,提供一个下拉列表选择是不是更好呢?继续干!

  找到源码目录下的SwaggerUi\CustomAssets\Index.html文件,添加一个id为select_baseUrl的select下拉选择框,并将input_baseurl输入框隐藏

修改swagger-ui-js下的window.SwaggerUi的render方法(要记得将index.html中的swagger-ui-min-js的引用改为swagger-ui-js)加入填充下拉数据的js代码以及添加下拉框触发事件

  找到SwaggerUi.Views.HeaderView,添加下拉事件

  重新编译后,刷新页面试试效果,可以下拉选择分组

结语

  关于swagger优化,鉴于本人水平有限,还有许多不足和错误的地方,烦请诸位大佬指正,叩谢!

关于Swagger优化的更多相关文章

  1. Webapi文档描述-swagger优化

    一.前言 最近做的项目使用WebApi,采取前后端分离的方式,后台提供API接口给前端开发人员.这个过程中遇到一个问题后台开发人员怎么提供接口说明文档给前端开发人员,最初打算使用word.Xmind思 ...

  2. 使用Swagger 搭建高可读性ASP.Net WebApi文档

    一.前言 在最近一个商城项目中,使用WebApi搭建API项目.但开发过程中,前后端工程师对于沟通接口的使用,是非常耗时的.之前也有用过Swagger构建WebApi文档,但是API文档的可读性并不高 ...

  3. Swagger使用教程 SwashbuckleEx

    一.前言 自从之前写了一篇<Webapi文档描述-swagger优化>这篇文章后,欠了大家一篇使用文档的说明,现在给大家补上哈. 二.环境 .Net Framework 4.5 WebAp ...

  4. ASP.NET WebApi 文档Swagger深度优化

    本文版权归博客园和作者吴双本人共同所有,转载和爬虫请注明博客园蜗牛原文地址,cnblogs.com/tdws   写在前面 请原谅我这个标题党,写到了第100篇随笔,说是深度优化,其实也并没有什么深度 ...

  5. ASP.NET WebApi 文档Swagger中度优化

    本文版权归博客园和作者吴双本人共同所有,转载和爬虫请注明原文地址:www.cnblogs.com/tdws   写在前面 在后台接口开发中,接口文档是必不可少的.在复杂的业务当中和多人对接的情况下,简 ...

  6. ABP项目中使用Swagger生成动态WebAPI

    本文是根据角落的白板报的<使用ABP实现SwaggerUI,生成动态webapi>一文的学习总结,感谢原文作者角落的白板报. 1 安装Swashbuckle.core 1.1 选择WebA ...

  7. Abp集成Swagger的最佳实践

    1.在项目中添加nuget包 Abp.Web.Api.SwaggerTool 2.在项目Abp模块的DependsOn添加AbpWebApiSwaggerToolModule Run It,启动项目, ...

  8. .Net Webapi Swagger增加登录功能

    .mytitle { background: #2B6695; color: white; font-family: "微软雅黑", "宋体", "黑 ...

  9. .Net Core 分布式微服务框架 - Jimu 添加 Swagger 支持

    系列文章 .Net Core 分布式微服务框架介绍 - Jimu .Net Core 分布式微服务框架 - Jimu 添加 Swagger 支持 一.前言 最近有空就优化 Jimu (一个基于.Net ...

随机推荐

  1. docker容器登录,退出等操作命令

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口 ...

  2. springDataRedis忽略实体指定的属性

    如果是通过 RedisRepository定义的实体,可能存在想要忽略的属性,那么,就可以 使用 org.springframework.data.annotation.Transient 注解,就可 ...

  3. 模糊查询 like 语句该怎么写?

    第 1 种:在 Java 代码中添加 sql 通配符. string wildcardname = "%smi%"; list<name> names = mapper ...

  4. synchronize、Lock、ReenTrantLock 的区别

    synchronize 和Lock: 1.synchronize 系java 内置关键字:而Lock 是一个类 2.synchronize 可以作用于变量.方法.代码块:而Lock 是显式地指定开始和 ...

  5. 数据分析之Pandas操作

    Pandas pandas需要导入 import pandas as pd from pandas import Series,DataFrame import numpy as np 1 Serie ...

  6. LVS集群和Keepalived高可用实战

    第四十章LVS集群和Keepalived高可用实战 一.ARP协议 1.概念 地址解析协议,即ARP(AddressResolutionProtocol),是根据IP地址获取物理MAC地址的一个TCP ...

  7. 简易shell脚本启动jar包

    可参考博客: Shell脚本中$0.$?.$!.$$.$*.$#.$@等的意义    https://blog.csdn.net/csgd2000/article/details/80396996 s ...

  8. Eclipse 从SVN检出项目之《文件夹 “” 已不存在 》

    1.eclipse 从svn检出项目 报文件夹不存在, 参考博客 https://blog.csdn.net/wenbsu/article/details/80965680 2.You need to ...

  9. SDT v0.0.1 上线

    自己的第一个开源组件,断断续续写了有一段时间,感觉可以发布 v0.0.1.SDT 是 SVG Drag Tree 的缩写,一个可以通过拖放 SVG 图标,来生成拥有树形结构的视图与相应数据的前端组件. ...

  10. (SSM框架)实现小程序图片上传(配小程序源码)

    阅读本文约"2分钟" 又是一个开源小组件啦! 因为刚好做到这个小功能,所以就整理了一下,针对微信小程序的图片(文件)上传! 原业务是针对用户反馈的图片上传.(没错,本次还提供小程序 ...