.netframework 关于Swagger优化

背景
尽管.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。
setControllerSummary: function () { var summaryDict = window.swaggerApi.swaggerObject.ControllerDesc; var id, controllerName, strSummary;
$("#resources_container .resource").each(function (i, item) {
id = $(item).attr("id"); if (id) {
controllerName = id.substring(9); try {
strSummary = summaryDict[controllerName]; if (strSummary) {
$(item).children(".heading").children(".options").first().prepend('<li class="controller-summary" style="color:green;" title="' + strSummary + '">' + strSummary + '</li>');
}
} catch (e) { console.log(e);
}
}
});
},修改完文件以后,再看看页面的加载,已经不会重复去访问v1。
二、接下来处理v1加载慢
先看看项目的的swagger配置:
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.IncludeXmlComments(GetXmlCommentsPath(thisAssembly.GetName().Name));
c.IncludeXmlComments(GetXmlCommentsPath("xxxx.Api.Dto"));
c.SingleApiVersion("v1", "xxxx.Api");
c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider));
})配置不多,其中有个CachingSwaggerProvider,实现了GetSwagger方法自定义返回数据,在这个方法里可以得知,实际上对api文档是有做缓存处理,v1加载的数据也就是这个SwaggerDocument。这也意味着,v1加载慢的原因出在这里。
public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
{
var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion);
SwaggerDocument srcDoc = null; //只读取一次
if (!_cache.TryGetValue(cacheKey, out srcDoc))
{
srcDoc = (_swaggerProvider as Swashbuckle.Swagger.SwaggerGenerator).GetSwagger(rootUrl, apiVersion);
srcDoc.vendorExtensions = new Dictionary<string, object> { { "ControllerDesc", GetControllerDesc() } };
_cache.TryAdd(cacheKey, srcDoc);
} return srcDoc;
}调试程序的时候,swashbuckle提供的GetSwagger方法占据了大量的耗时。将源码Swashbuckle.Core引用进来,重新打开swagger时会有个小问题,资源文件都报404错误,这个是因为嵌入资源文件没有找到
<ItemGroup> <EmbeddedResource Include="..\swagger-ui\dist\**\*.*"> <LogicalName>%(RecursiveDir)%(FileName)%(Extension)</LogicalName> <InProject>false</InProject> </EmbeddedResource> </ItemGroup>
根据路径查看,swagger-ui下是空白的。将从其他地方找到的或者从反编译文件里整理出来的文件放到该目录下,并将swagger-ui作为依赖项,重新编译项目后swagger页面加载资源文件就正常了。(如果有遇到依旧找不到资源文件的情况,重新再添加一次依赖项编译项目即可)
接下来就可以开始调试了,经过一番波折,最终将元凶定位到了SwaggerGenerator中GetSwagger方法里获取paths这个地方,实际上就是在使用CreatePathItem的时候耗时过久
var paths = GetApiDescriptionsFor(apiVersion) .Where(apiDesc => !(_options.IgnoreObsoleteActions && apiDesc.IsObsolete())) .OrderBy(_options.GroupingKeySelector, _options.GroupingKeyComparer) .GroupBy(apiDesc => apiDesc.RelativePathSansQueryString()) .ToDictionary(group => "/" + group.Key, group => CreatePathItem(group, schemaRegistry));
刚开始尝试用多线程的方式进行处理,尽管确实能够缩短获取json数据的时间,但依旧有两个问题:
线程不安全,时不时页面会报错
即使能快速返回json数据,页面渲染耗慢的问题依旧未解决。正如前面我们的项目中GetSwagger是使用到缓存的,在重新刷新swagger时,依旧存在卡慢问题。
三、将需返回json数据
优化swagger加载,需要同时考虑到前端渲染页面以及后端梳理json数据所导致的页面加载慢问题。有什么好的办法么?swashbuckle core版本是支持分组的,但是项目使用的Framework版本不支持,既然不支持,就直接改造源码,按控制器分组,说干就干:
找到HttpConfigurationExtensions类的EnableSwagger方法,这个方法用来配置路由
public static SwaggerEnabledConfiguration EnableSwagger(
this HttpConfiguration httpConfig, string routeTemplate,
Action<SwaggerDocsConfig> configure = null)
{
var config = new SwaggerDocsConfig(); if (configure != null) configure(config);
httpConfig.Routes.MapHttpRoute(
name: "swagger_docs" + routeTemplate,
routeTemplate: routeTemplate,
defaults: null,
constraints: new { apiVersion = @".+" },
handler: new SwaggerDocsHandler(config)
);
//配置控制器路由
string controllRouteTemplate=DefaultRo.netframework 关于Swagger优化
声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。如若本站内容侵犯了原著者的合法权益,可联系本站删除。







