ABP给WebApi添加性能分析组件Miniprofiler

在ABP的WebApi中,对其性能进行分析监测是很有必要的。而悲剧的是,MVC项目中可以使用的MiniProfiler或Glimpse等,这些都不支持WebApi项目,而且WebApi项目通常也没有界面,不能进行性能分析的交互。在上一篇教程中,通过集成SwaggerUI解决了界面的问题。在这篇文章中,我们就来一步步实现为WebApi项目集成Miniprofiler。集成后,我们可以监控EF执行效率,执行语句,页面执行时间等,这些结果将以很友好的方式显示在界面上。

问题分解

本质上,集成Miniprofiler-Swagger可以分解为三个问题:

  1. 怎样监测一个WebApi项目的性能。
  2. 将性能分析监测信息从后端发送到UI。
  3. 在UI显示分析监测结果。

一、分析WebApi项目性能

安装Miniprofiler
在我们准备做Miniprofiler集成前,先思考一下Miniprofiler在监测MVC项目时是怎么做的,以便借鉴一下实现思路。当一个控制器执行后,Miniprofiler通过一个guid跟踪请求执行的每一步的时间。执行活动完成后,跟踪监测结果暂存在内存中。然后Miniprofiler通知其js模块有一个新动作执行完成了。这使得其程序终端发出调用,请求获取实时的结果。现在的问题是这是一个MVC终端,幸运的是在WebApi项目中构造这样一个终端也很容易。

  1. 首先安装Miniprofiler
    • Install-Package Miniprofiler。
    • 我们只需要Miniprofiler core,不需要Miniprofiler.mvc,但是注意一定要安装V3之后的版本。
    • ABP中,安装在Web项目和WebApi中。
  2. Install-Package Microsoft.AspNet.Mvc
    • ABP中,Web项目本身就有了,WebApi项目不需要安装。
  3. 将以下代码添加到web.config中的<handlers>节下。
<add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" 
type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" 
preCondition="integratedMode" />
  1. 将以下代码添加到Global.asax
protected void Application_BeginRequest()
{
    MiniProfiler.Start();
}
protected void Application_EndRequest()
{
    MiniProfiler.Stop();
}
  1. 测试一下所做的是否正确
    • 重新编译运行程序,在swagger中执行一个动作
    • 访问http://localhost/mini-profiler-resources/results
    • 正常执行结果类似如下

二、将分析监测信息从后台发送到UI

将Miniprofiler元数据注入swashbuckle
在MVC项目中,我们通常在razor视图中添加一个辅助方法RenderInclude,从而注入一些代码执行和渲染分析监测结果盒子。然而在WebApi中是没有页面可以注入的,所以让我们来点创新吧。
我们观察下RenderInclude的输出,可以看到Miniprofiler注入了一个异步的脚本标签。这个标签从Miniprofiler终端下载了一个js脚本,然后脚本运行展示出分析监测结果。
我们知道在swagger中可以注入js脚本,例如我们上节教程中注入的汉化js文本,然后脚本会在swaggerUI上运行。这就足够我们进行上面的步骤了。

  1. 首先我们来添加一个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的联系人名称,真是太有才了 ^_^ 
  1. 反注释swaggerconfig.cs中的documentfilter代码,并将其指向刚才创建的filter
    c.DocumentFilter<InjectMiniProfiler>();
  2. 编译运行并刷新swagger页面,可以看到创建人是 script async... _
  3. 我们已经获得了在客户端需要的脚本。现在我们只需要用js将其改进一下。

三、在UI上显示分析监测结果

在swagger页面上运行Miniprofiler Js

  1. 添加js文件SwaggerUiCustomization.js
  2. 设置js文件属性为嵌入的资源,以确保其总是被拷贝到输出目录
  3. 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('');
  1. 修改swaggerconfig.cs的InjectJavaScript,将上面创建的js注入。
string resourceName2 = thisAssembly.FullName.Substring(0, thisAssembly.FullName.IndexOf(",")) + ".Scripts.swaggerui.SwaggerUiCustomization.js";
 c.InjectJavaScript(thisAssembly, resourceName2);
  1. 重新编译运行,刷新swagger页面,可以看到注入的文本从swagger页面上消失了,而Miniprofiler弹出层出现在页面左上角。

四、实时刷新监测结果

蛋疼的是,我们还没有完全搞定。你可以执行一个Swagger接口,然后发现Miniprofiler并未跟着更新结果,那上面这一切都没有意义了。幸运的是,Miniprofiler在MVC网站中被设计为同样可以使用AJAX调用。因此,我们可以利用这一点进行改进。扫读Miniprofiler的JS代码,我们发现它可以监听angular页面应用的xhr对象。

  1. 我们往SwaggerUiCustomization.js文件中添加代码window.angular=true;,假装我们使用angular。
    • 这就是我们要使用MiniprofilerV3.2版本的原因。V3.0版本在xhr监听上有个bug,会导致stackoverflow错误。如果你的浏览器控制台抛出stackoverflow异常,那你应该再次检查是否使用了正确的Miniprofiler版本。
  2. 重新编译运行程序,刷新Swagger页面。
  3. 到此为止我们仅仅实现了可以让Miniprofiler实时监听新的请求调用。为了显示出监测信息,还需要传递所监测的活动的ID列表。这个可以用另外一个filter轻松实现。
    • ABP项目由于存在Web项目,所以可以不必再按下面步骤处理。
  4. 在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);
        }
    }
  1. WebApiConfig.cs中注册这个Filterconfig.Filters.Add(new WebApiProfilingActionFilter());
    • ABP项目中应在WebApiModule中的Initialize()方法中,添加配置Configuration.Modules.AbpWebApi().HttpConfiguration.Filters.Add(new WebApiProfilingActionFilter());
  2. 重新编译运行程序,刷新Swagger页面,之后在每次执行接口后,会发现左上角的分析监测界面会实时增加一行结果。另外,在HTTP响应头中会看见一个类似的记录"x-MiniProfiler-ids": "[\"b9512f60-9e75-42f9-bdc1-597695e5b745\"]",
  3. 大功告成!再多添加几个接口,试试效果吧~

五、总结

审视一遍最终的代码,看起来很简单,但是这个过程中是作了很多的尝试和试错,来让swashbuckle和Miniprofiler集成起来,同时做了一些重构来减少代码量,但是我对最终结果还是非常满意的。如果swashbuckle能有更简便的传递元数据的方法,那就更好啦。另外,我知道它支持定制前端页面,但是在这里我们只是要显示出监测分析结果就达到目的,所以只需要做一些调整,而不是从头开始做一个页面。
这些都实现后,我们就可以进一步使用所有的Miniprofiler插件了,譬如Entity framework profiling。
下一篇教程中,我们将把Miniprofiler EF6集成到项目中,以实现EF的监测分析。

参考链接:http://www.lambdatwist.com/webapi-profiling-with-miniprofiler-swagger/

posted @ 2018-01-28 16:12  览岳  阅读(3184)  评论(8编辑  收藏  举报