在ABP的WebApi中,对其性能进行分析监测是很有必要的。而悲剧的是,MVC项目中可以使用的MiniProfiler或Glimpse等,这些都不支持WebApi项目,而且WebApi项目通常也没有界面,不能进行性能分析的交互。在上一篇教程中,通过集成SwaggerUI解决了界面的问题。在这篇文章中,我们就来一步步实现为WebApi项目集成Miniprofiler。集成后,我们可以监控EF执行效率,执行语句,页面执行时间等,这些结果将以很友好的方式显示在界面上。
问题分解
本质上,集成Miniprofiler-Swagger可以分解为三个问题:
- 怎样监测一个WebApi项目的性能。
- 将性能分析监测信息从后端发送到UI。
- 在UI显示分析监测结果。
一、分析WebApi项目性能
安装Miniprofiler
在我们准备做Miniprofiler集成前,先思考一下Miniprofiler在监测MVC项目时是怎么做的,以便借鉴一下实现思路。当一个控制器执行后,Miniprofiler通过一个guid跟踪请求执行的每一步的时间。执行活动完成后,跟踪监测结果暂存在内存中。然后Miniprofiler通知其js模块有一个新动作执行完成了。这使得其程序终端发出调用,请求获取实时的结果。现在的问题是这是一个MVC终端,幸运的是在WebApi项目中构造这样一个终端也很容易。
- 首先安装Miniprofiler
- Install-Package Miniprofiler。
- 我们只需要Miniprofiler core,不需要Miniprofiler.mvc,但是注意一定要安装V3之后的版本。
- ABP中,安装在Web项目和WebApi中。
- Install-Package Microsoft.AspNet.Mvc
- ABP中,Web项目本身就有了,WebApi项目不需要安装。
- 将以下代码添加到web.config中的
<handlers>
节下。
<add name="MiniProfiler" path="mini-profiler-resources/*" verb="*"
type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified"
preCondition="integratedMode" />
- 将以下代码添加到Global.asax
protected void Application_BeginRequest()
{
MiniProfiler.Start();
}
protected void Application_EndRequest()
{
MiniProfiler.Stop();
}
- 测试一下所做的是否正确
- 重新编译运行程序,在swagger中执行一个动作
- 访问http://localhost/mini-profiler-resources/results
- 正常执行结果类似如下
二、将分析监测信息从后台发送到UI
将Miniprofiler元数据注入swashbuckle
在MVC项目中,我们通常在razor视图中添加一个辅助方法RenderInclude
,从而注入一些代码执行和渲染分析监测结果盒子。然而在WebApi中是没有页面可以注入的,所以让我们来点创新吧。
我们观察下RenderInclude
的输出,可以看到Miniprofiler注入了一个异步的脚本标签。这个标签从Miniprofiler终端下载了一个js脚本,然后脚本运行展示出分析监测结果。
我们知道在swagger中可以注入js脚本,例如我们上节教程中注入的汉化js文本,然后脚本会在swaggerUI上运行。这就足够我们进行上面的步骤了。
- 首先我们来添加一个swagger的IDocumentFilter。
- 这个filter在每次swagger页面加载时都会执行。
public class InjectMiniProfiler : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
swaggerDoc.info.contact = new Contact()
{
name = MiniProfiler.RenderIncludes().ToHtmlString()
};
}
}
* 我们把Miniprofiler的渲染脚本标签赋给了swaggerDoc的联系人名称,真是太有才了 ^_^
- 反注释swaggerconfig.cs中的documentfilter代码,并将其指向刚才创建的filter
c.DocumentFilter<InjectMiniProfiler>();
- 编译运行并刷新swagger页面,可以看到创建人是 script async... _
- 我们已经获得了在客户端需要的脚本。现在我们只需要用js将其改进一下。
三、在UI上显示分析监测结果
在swagger页面上运行Miniprofiler Js
- 添加js文件
SwaggerUiCustomization.js
- 设置js文件属性为嵌入的资源,以确保其总是被拷贝到输出目录
- Js代码如下,这段代码主要是获取我们之前注入的脚本文本(使用JQuery),创建新的脚本标签,并附加到DOM,脚本会随后执行。
//Create a mini profiler script tag with the right properites
var MiniProfiler = $('#api_info > div:nth-child(3)').text();
const attributes = [
'src', 'data-version', 'data-path', 'data-current-id', 'data-ids',
'data-position', 'data-trivial', 'data-children', 'data-max-traces', 'data-controls',
'data-authorized', 'data-toggle-shortcut', 'data-start-hidden', 'data-trivial-milliseconds'
];
var GetAttr = function (input, attributeName) {
const myRegexp = attributeName + '="(.*?)"';
const re = new RegExp(myRegexp, "g");
const match = re.exec(input);
return match[1];
}
var s = document.createElement("script");
s.type = "text/javascript";
s.id = "mini-profiler";
s.async = true;
for (var i = 0; i < attributes.length; i++) {
var element = attributes[i];
s.setAttribute(element, GetAttr(MiniProfiler, element));
}
document.body.appendChild(s);
// Remove injected tag from view
$('#api_info > div:nth-child(3)').text('');
- 修改swaggerconfig.cs的InjectJavaScript,将上面创建的js注入。
string resourceName2 = thisAssembly.FullName.Substring(0, thisAssembly.FullName.IndexOf(",")) + ".Scripts.swaggerui.SwaggerUiCustomization.js";
c.InjectJavaScript(thisAssembly, resourceName2);
- 重新编译运行,刷新swagger页面,可以看到注入的文本从swagger页面上消失了,而Miniprofiler弹出层出现在页面左上角。
四、实时刷新监测结果
蛋疼的是,我们还没有完全搞定。你可以执行一个Swagger接口,然后发现Miniprofiler并未跟着更新结果,那上面这一切都没有意义了。幸运的是,Miniprofiler在MVC网站中被设计为同样可以使用AJAX调用。因此,我们可以利用这一点进行改进。扫读Miniprofiler的JS代码,我们发现它可以监听angular页面应用的xhr对象。
- 我们往SwaggerUiCustomization.js文件中添加代码
window.angular=true;
,假装我们使用angular。- 这就是我们要使用MiniprofilerV3.2版本的原因。V3.0版本在xhr监听上有个bug,会导致stackoverflow错误。如果你的浏览器控制台抛出stackoverflow异常,那你应该再次检查是否使用了正确的Miniprofiler版本。
- 重新编译运行程序,刷新Swagger页面。
- 到此为止我们仅仅实现了可以让Miniprofiler实时监听新的请求调用。为了显示出监测信息,还需要传递所监测的活动的ID列表。这个可以用另外一个filter轻松实现。
- ABP项目由于存在Web项目,所以可以不必再按下面步骤处理。
- 在WebApi中新建Filter文件夹,创建WebApiProfilingActionFilter.cs。
public class WebApiProfilingActionFilter : ActionFilterAttribute
{
public const string MiniProfilerResultsHeaderName = "X-MiniProfiler-Ids";
public override void OnActionExecuted(HttpActionExecutedContext filterContext)
{
var MiniProfilerJson = JsonConvert.SerializeObject(new[] {MiniProfiler.Current.Id});
filterContext.Response.Content.Headers.Add(MiniProfilerResultsHeaderName, MiniProfilerJson);
}
}
- 在
WebApiConfig.cs
中注册这个Filterconfig.Filters.Add(new WebApiProfilingActionFilter());
- ABP项目中应在
WebApiModule
中的Initialize()
方法中,添加配置Configuration.Modules.AbpWebApi().HttpConfiguration.Filters.Add(new WebApiProfilingActionFilter());
- ABP项目中应在
- 重新编译运行程序,刷新Swagger页面,之后在每次执行接口后,会发现左上角的分析监测界面会实时增加一行结果。另外,在HTTP响应头中会看见一个类似的记录
"x-MiniProfiler-ids": "[\"b9512f60-9e75-42f9-bdc1-597695e5b745\"]",
- 大功告成!再多添加几个接口,试试效果吧~
五、总结
审视一遍最终的代码,看起来很简单,但是这个过程中是作了很多的尝试和试错,来让swashbuckle和Miniprofiler集成起来,同时做了一些重构来减少代码量,但是我对最终结果还是非常满意的。如果swashbuckle能有更简便的传递元数据的方法,那就更好啦。另外,我知道它支持定制前端页面,但是在这里我们只是要显示出监测分析结果就达到目的,所以只需要做一些调整,而不是从头开始做一个页面。
这些都实现后,我们就可以进一步使用所有的Miniprofiler插件了,譬如Entity framework profiling。
下一篇教程中,我们将把Miniprofiler EF6集成到项目中,以实现EF的监测分析。
参考链接:http://www.lambdatwist.com/webapi-profiling-with-miniprofiler-swagger/