Profiling WebApi projects

http://www.lambdatwist.com/webapi-profiling-with-miniprofiler-swagger/

Introduction

In the last few weeks I’ve been working on a couple of WebApi projects and one thing I have been missing, was a good profiling tool to keep an eye on the performance of the controller actions. A quick search concluded that there was no suitable solution; there are a number of stand alone profilers, like Stackify that do a satisfactory job, but since it is running on your local machine its not straightforward to profile your code once deployed on a different environment. Developers that used MVC in the past have tools like MiniProfiler and Glimpse that run as part of the web project and shows on the fly results which makes them more convenient to use.

Sadly both MiniProfiler and Glimpse don’t work out of the box with WebApi for two main reasons: although WebApi and MVC share similar patterns, they are not exactly the same and run on different namespaces and dlls; secondly WebApi does not usually have a GUI to interact with.

The lack of GUI can be solved with other tools like Swagger and its awesome wrapper for .net swashbuckle, and lack of MiniProfiler support for WebApi can be hacked in. So then its a matter of combining the two.

This article explains how to accomplish that. So let’s get started!

Breaking down the problem

So in essence, using MiniProfiler with swagger can be broken down to three parts:

  1. How to profile a WebApi project.
  2. How to send profiling information from backend to the UI
  3. Show profiling results in the UI

Setup

Before we start solving the problem let’s set up an example project.

  • First let’s create a WebApi project.
    Create project!!
    Create project!!
  • Following the above steps should create the following structure.
    Create project!!
  • Create a controller (which I’m calling default ) and add the following code

using System.Web.Http;

namespace WebApi_MiniProfiler_Swagger.Controllers
{
    [RoutePrefix("api/v1/default")]
    public class DefaultController : ApiController
    {
        [Route("")]
        public IHttpActionResult Get()
        {
            return Json(new
            {
                Name = "Marco",
                Description = "I need some profiling!`"
            });
        }
    }
}

  • Compile and navigate to the url api and you should see the action output
    Create project!!

Now let`s add swagger to our project

  • Open the nuget console and install swashbuckle Install-Package Swashbuckle
  • Compile the project again and navigate to your root url /swagger
    swagger view

1. How to profile a WebApi project.

Setting up MiniProfiler

Before we get into how to integrate MiniProfiler its useful to have a general idea of what MiniProfiler does when profiling an mvc project. When a controller action is exectued, mini profiler keeps track of the execution times using a guid for every request. When the action completes the result is stored in memory. MiniProfiler then signals its javascript module that a new action has executed. This then makes a call to a special MiniProfiler endpoint to get the timing results. Unfortunately this is an mvc endpoint but luckily exposing this endpoint in a WebApi project is straightforward.

  • First let`s install MiniProfiler.
    • Install-Package MiniProfiler
    • We do not need the MiniProfiler.mvc library only the MiniProfiler core but make sure you install the latest v3 module. At the time of writing thats version 3.2.x I explain further down why version 3.0 does not work.
  • Install-Package Microsoft.AspNet.Mvc
  • Add the following to your web.config under <handlers>
<add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" 
type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" 
preCondition="integratedMode" />

  • Add the following code to Global.asax.cs
protected void Application_BeginRequest()
{
    MiniProfiler.Start();
}

protected void Application_EndRequest()
{
    MiniProfiler.Stop();
}


2. How to send profiling information from backend to the UI

Inject MiniProfiler metadata in swashbuckle

In an mvc project we normally add a helper method RenderInclude in our razor views, this injects some code that executes and renders the profiling boxes. Since in WebApi we don’‘t usually have a page to inject too we’‘ll have to be a bit more creative.

If we look at the output of RenderIncludes we observe that mini profiler inserts an async script tag. This tag then downloads a JS script from the MiniProfiler endpoint which is then run to display the profiling results.

Looking at the swagger documentation there is a way of injecting information as text, and it also allows for javascript to be run with the page. That should be enough the get us going.

  • First lets add a swagger document filter.
    • This filter executes every time the swagger page is loaded
    • The code below takes the MiniProfiler script tag and inserts it as the api author (very hacky but bear with me)
using System.Web.Http.Description;
using StackExchange.Profiling;
using Swashbuckle.Swagger;

namespace WebApi_MiniProfiler_Swagger.Filters
{
    public class InjectMiniProfiler : IDocumentFilter
    {
        public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
        {
            swaggerDoc.info.contact = new Contact()
            {
                name = MiniProfiler.RenderIncludes().ToHtmlString()
            };
        }
    }
}

  • Uncomment the documentfilter code in swaggerconfigs.cs add point it to the newly created filter c.DocumentFilter<InjectMiniProfiler>();

  • Compile and refresh the swagger page and you see that the api was created by Mr script async.
    MiniProfiler view
  • As you can see we have the information we need on the client side. Now we just need to process it with some javascript.

3. Show profiling results in the UI

How to run MiniProfiler JS on a swagger page.

  • Add a javascript file to the project and call it SwaggerUiCustomization.js
  • Change it to be an embedded resource and make sure it is always copied to the output directory
  • Then add the following javascript
    • This code basically finds the text we injected (using JQuery), creates a new script tag with that information, then appends it to the DOM. This then executes the javascript.
//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('');

  • Recompile and refresh swagger

  • Uncomment the injectjavascript attribute inside swaggerconfig.cs and point it to the javascript you created. E.g c.InjectJavaScript(thisAssembly, "WebApi_MiniProfiler_Swagger.App_Start.SwaggerUiCustomization.js");

  • The injected text should have disappeared and the MiniProfiler popup should appear on the left
    MiniProfiler view
  • Yay !!

Unfortunately we are not done. If you click on the GET action try it out button, MiniProfiler does not update making the whole thing useless. Luckily for us MiniProfiler is designed to also work with ajax calls in mvc websites, so with a few more hacks we can get it to work. Scanning through the MiniProfiler js code we find that it can listen to the xhr object of the page if its an angular app.

  • So lets add window.angular = true; to our SwaggerUiCustomization.js file and pretend we are using angular.
    • Note: This is why it’s important to use version 3.2 of MiniProfiler. version 3.0 had a bug with the xhr listener that caused stackoverflow errors. If your browser console throws stackoverflow exceptions, double check you are running the correct version of MiniProfiler.
  • Recompile the solution and refresh swagger.
  • So far we only enables MiniProfiler to listen to new calls. For the information to display we also need to transmit the IDS of the profiled actions.
  • This can be easily done using another filter.
  • Create a new class and call it WebApiProfilingActionFilter.cs and add the following code:
using System.Web.Http.Filters;
using Newtonsoft.Json;
using StackExchange.Profiling;

namespace WebApi_MiniProfiler_Swagger.Filters
{
    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);
        }
    }
}

  • In WepApiConfig.cs register the filter config.Filters.Add(new WebApiProfilingActionFilter());

  • Recompile and give it another try.

  • Every time you run an action a new box should be added on the left hand side.

  • In addition in the response headers you should see a similar entry "x-MiniProfiler-ids": "[\"b9512f60-9e75-42f9-bdc1-597695e5b745\"]",

  • So in conclusion. Double yay!!

  • Now lets add a few more actions and make sure everything works

   [Route("step")]
    public IHttpActionResult GetWithStep()
    {
        var profiler = MiniProfiler.Current;

        using (profiler.Step("Starting a profiling Step"))
        {
            return Json(new
            {
                Name = "Marco",
                Description = "I haz profiling!`"
            });
        }
    }

    [Route("notfound")]
    public IHttpActionResult NothingToSeeHere()
    {
        return NotFound();
    }

    [HttpPost]
    [Route("posting")]
    public IHttpActionResult PostSomething(string stuff)
    {
        return Ok();
    }

    [Route("broken")]
    public IHttpActionResult ThrowAnError()
    {
        throw new Exception("Error error");
    }

  • Try out the new actions, and with every execution the popup should update with more timings. Looks good to me!
    MiniProfiler view

Conclusion

Looking at the code in its final form makes it look simple enough, but it took a fair amount of trial and error to get swashbuckle and MiniProfiler working and some more refactoring to cut down the code, but I am pretty pleased with the result. It would be nice if swashbuckle has an easier way to pass down metadata into swagger. I know they support custom front pages but what we needed here is a small tweak rather than build a whole page from scratch. MiniProfiler could provide out of the box support for WebApi since its one of the most used profiling tools.

Once all this is set up we can use all the other MiniProfiler plugins such as Entity framework profiling. Works out of the box.

I uploaded a full example of the code in the this post here, hope you will find it useful.

If you have any questions, comments or suggestions, please leave them below.

Some useful links: MiniProfiler Swashbuckle

对应的中文版有# ABP给WebApi添加性能分析组件Miniprofiler

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,251评论 0 10
  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,238评论 0 23
  • 今天是开始锻炼第二天,嘿嘿,今天就木有锻炼,才第二天~ 今早吃了碗卷粉,中午吃了个馒头,下午3点开始烤烧烤一直吃吃...
    良辰可黛阅读 168评论 0 0
  • 文/啸谷 小时候起,父亲就经常告诉我读书的意义,也买了很多书,但因为贪玩,一直到上大学前并没有读多少。上大学后,好...
    啸谷阅读 593评论 4 11
  • 我想对你说 偶翻手机,见老师要求班级征文,主题为:“我想对你说”。 说些什么呢?...
    杨书海阅读 179评论 0 0