{"id":1715,"date":"2013-10-29T00:00:00","date_gmt":"2013-10-25T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/building-performance-metrics-into-asp-net-mvc-applications\/"},"modified":"2026-05-25T16:09:46","modified_gmt":"2026-05-25T16:09:46","slug":"building-performance-metrics-into-asp-net-mvc-applications","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/building-performance-metrics-into-asp-net-mvc-applications\/","title":{"rendered":"ASP.NET MVC Performance Monitoring: Custom Metrics, Performance Counters, and Application Insights Integration"},"content":{"rendered":"\n<p><b>Production ASP.NET MVC applications need performance telemetry to answer basic operational questions: which actions are slow? Are request times degrading? Are certain endpoints throwing errors? Are key business metrics (sign-ups per hour, orders per minute) where expected?<\/b><\/p>\n\n\n\n<p><b>This article walks through implementing each of 3 instrumentation approaches: custom counters via PerformanceCounter class, ASP.NET MVC action filters for per-action timing, and Application Insights SDK integration for production telemetry. <\/b><\/p>\n\n\n\n<p><em><strong>Note: <\/strong>For modern .NET applications (ASP.NET Core on .NET 6+), the equivalent ecosystem is OpenTelemetry (vendor-neutral observability standard), Application Insights still works, and additional options (Prometheus + Grafana, Datadog .NET Tracer) are commonly used. The instrumentation patterns (action filters, middleware, custom metric definition) transfer conceptually.<\/em><\/p>\n\n\n\n<div id=\"pretty\">\n<p>As application developers, we are always interested in performance. Applications today are more complex, support more users and are more essential to the daily operation of most enterprises than even a few years ago. This means that we are in a permanent state of needing better tools and better data to monitor the usage, performance and health of the applications we build. While it is still important to profile, load test and stress-test applications, we would also like to have a way to collect detailed performance data about an application while it is running in production. Why is this so? There are several good reasons.<\/p>\n<ul>\n<li>You gain actual data of how your application is performing <em>in production<\/em>. This data can help you to understand what features are most frequently used so you then know where to focus your future performance tuning efforts.<\/li>\n<li>You can establish a baseline of how your application performs under normal conditions. Knowing what is normal will then help you to understand when the health and performance of the application starts to trend yellow or enter red.<\/li>\n<li>When a performance problem happens, you will have the necessary detailed performance data in place to quickly diagnose it.<\/li>\n<li>You can understand usage trends that vary during the day or from day to day by instrumenting your application. The data will show if there is a certain time when usage spikes, and help you understand if you have enough capacity to handle these peak-load events.<\/li>\n<\/ul>\n<p>Most applications contain very little instrumentation for usage or performance. If we are fortunate, there may be a few log statements scattered throughout the application. A better solution would be to develop a consistent approach that could be applied across the application, making sure that a baseline set of metrics is collected for each major action that the application performs. As it turns out, the design of the ASP.NET MVC framework makes this easy. This article will show how to do it.<\/p>\n<p>In designing a solution, you&#8217;ll probably have these goals.<\/p>\n<ul>\n<li>The solution should be easy to apply across the project. If it requires a developer to do something every time that an action or controller is added, then it is doomed to failure.<\/li>\n<li>The solution should not only be easy to implement in new applications that we are creating but also easy to retrofit to existing applications.<\/li>\n<li>The code that tracks performance must be cleanly separated from the application code. It is good practice in any design to keep different concerns separated, and there is no good reason to mingle the performance tracking code with the application logic.<\/li>\n<li>Performance data should be formatted to make it easy to collect and analyze using existing toolsets. This facilitates a single view of the data across the entire technology stack and allows existing reporting and alerting tools to be used.<\/li>\n<li>Although we&#8217;d want to track data across the application, the application developer does not necessarily want to track performance in some places. So the solution should be designed to allow the developer to opt out of certain actions.<\/li>\n<\/ul>\n<p>An overview of the solution is shown in the figure below.<\/p>\n<p>In the ASP.NET MVC framework, custom action filters allow an application developer to intercept calls to controller actions and add pre and post processing steps to the action.\u00a0 In this way, action filters provide a way to implement cross cutting concerns like logging or security in a module that is defined independently of the application code.\u00a0 In this design, a custom action filter named <code>MvcPerformanceAttribute<\/code> is responsible for intercepting calls to controller actions and provides the entry point for where the performance of a controller action can be measured.<\/p>\n<p>When an incoming request is received, the <code>OnActionExecuting()<\/code> method will create a <code>PerformanceTracker<\/code> object and attach it to request.\u00a0 The <code>PerformanceTracker<\/code> object is responsible for measuring how long the execution of the action takes and coordinating the update of the custom performance counters.\u00a0 After attaching the <code>PerformanceTracker<\/code> object to the request, the <code>ProcessActionStart()<\/code> method of the <code>PerformanceTracker<\/code> object is called to start measuring performance.\u00a0 After the controller action has completed, the outbound response will again pass through the <code>MvcPerformanceAttribute<\/code>, this time through the <code>OnActionExecuted()<\/code> method.\u00a0 This method will extract the <code>PerformanceTracker<\/code> object off of the request and call the <code>ProcessActionComplete()<\/code> method.\u00a0 Calling this method indicates that the performance measurement is complete and the appropriate performance counters will be updated.<\/p>\n<p>Updating the custom performance counter objects is accomplished by making use of a collection of objects that derive from the <code>PerformanceMetricBase class<\/code>. \u00a0These objects hold a reference to the performance counter(s) they are responsible for updating, and take care of the details of how to accomplish the update.\u00a0 By delegating the responsibility for the actual update of counters into a separate set of objects, the solution allows for future extensibility and keeps the process of updating the counters decoupled from the <code>PerformanceTracker<\/code> object.\u00a0 If a developer wishes to add an additional counter in the future, the simply write a new class that derives from <code>PerformanceMetricBase<\/code> and have it added to the collection.\u00a0 They do not need to edit the internals of the <code>PerformanceTracker <\/code> class.<\/p>\n<div class=\"note\">\n<p class=\"note\"><strong>Note: <\/strong>In order to better demonstrate the instrumentation framework, I&#8217;ve provided a target MVC application to which it is being applied. This can be downloaded <a href=\"https:\/\/www.simple-talk.com\/content\/file.ashx?file=9699\">from here<\/a>, or the top of the article.\u00a0 The target application is the MVC Music Store developed by Jon Galloway as a tutorial on <a href=\"http:\/\/www.asp.net\">www.asp.net<\/a>. The source code for this application is available on Codeplex under the Microsoft Public License (Ms-Pl).\u00a0\u00a0 My changes are limited to converting the application to MVC 4 and integrating the instrumentation framework into it.\u00a0\u00a0 <br \/>Credit for developing the original Music Store application belongs to Mr. Galloway and the team at www.asp.net.<\/p>\n<\/div>\n<h2>Custom Performance Counters<\/h2>\n<p>Performance data captured by this solution will be published to a set of custom performance counters. Probably the largest advantage of doing this is that there are a number of tools available that are designed to consume performance counter data, report on it and create alerts based on it. Many operations groups will already have tooling in place to monitor performance counter data from their Windows servers or from applications such as SQL Server. Therefore, it will be very easy for these groups to now consume application-level data. This also offers the advantage of collecting data from the entire technology stack in one place. Even if your organization does not own third-party tooling, you can still use the <code> perfmon<\/code> tool built into Windows to overlay the data collected by this solution with server level data about CPU, memory and network IO.<\/p>\n<p>For each application, a new multi-instance performance counter category must \u00a0be created. I have provided a PowerShell script to do this which I&#8217;ll describe later. Each instance within this multi-instance category will represent an individual controller action. The instance names will be dynamically created at runtime, based on the name of the controller and the action that performance data is being tracked for. By creating instance names dynamically based on the name of the action method, no additional work is required to track the data when new controllers and actions are added to an application.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1889-clip_image002-630x469.jpg\" alt=\"1889-clip_image002-630x469.jpg\" width=\"630\" height=\"469\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>The table below lists the metrics that will be captured for each MVC action<\/p>\n<table class=\"MsoTableClassic4\">\n<tbody>\n<tr>\n<td valign=\"top\">\n<p><b><i>Counter Name<\/i><\/b><i><\/i><\/p>\n<\/td>\n<td valign=\"top\">\n<p><b><i>Description<\/i><\/b><i><\/i><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Total Calls<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The total number of times this action has been called since the ASP.NET worker process has been started<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Total Elapsed Time<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The total elapsed time (in milliseconds) spent in this controller action since the ASP.NET worker process started<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Delta Calls<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The number of times this action has been called in the last sample period. That is, if the sampling period in a tool such as perfmon is 30 seconds, this value represents that number of calls in the last 30 seconds<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Delta Elapsed Time<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The elapsed time (in milliseconds) spent in this action for the last sample period. That is, if the sampling period in a tool such as perfmon is 30 seconds, this value represents that number of calls in the last 30 seconds<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Average Time per Call<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The average amount of elapsed time taken for calls to this action in the last sample period. This value is in seconds (so 0.500 is 500 milliseconds).<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Calls Per Second<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The number of times that this controller action was called per second for the last reporting period<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Calls in Progress<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The number of requests currently executing this action. This is a point in time measurement, not an average.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Last Call Elapsed Time<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The elapsed time taken by \u00a0the last completed call to this action. This is a point in time measurement that represents whatever call was last completed when the counter is checked. It is not an average value.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Total Exceptions Thrown<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The total number of uncaught exceptions thrown by this controller action since the ASP.NET worker process started. If the number in this counter increases, it will indicate an issue with the application&#8217;s health<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">\n<p><b>Delta Exceptions Thrown<\/b><\/p>\n<\/td>\n<td valign=\"top\">\n<p>The number of uncaught exceptions thrown in the last period. Non-zero values in this counter indicate an issue with the applications health<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>As mentioned above, the performance counter category must be created prior to running the application. You will need administrator-level permissions to create performance counters, so be sure to run the included PowerShell script, <code> CreatePerformanceCounters.ps1<\/code> as an administrative user. The PowerShell script takes two argu \u00adments, the name of the performance counter category and the name of an XML file that contains data about what counters to create. If additional counters are ever added to the solution, then the included XML file should be updated so that these counters are created. An example of running the script is shown below. (click on the image to see it full-size)<\/p>\n<figure><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1889-clip_image003.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1889-clip_image003-630x372.jpg\" alt=\"1889-clip_image003-630x372.jpg\" width=\"630\" height=\"372\" \/><\/a><\/figure>\n<p>\u00a0<\/p>\n<h2>Creating the Custom Action Filter<\/h2>\n<p>The next step in the solution is the <code>MvcPerformanceAttribute<\/code> class.\u00a0 This class extends <code>System.Web.Mvc. \u00adActionFilterAttribute<\/code> and overrides the <code>OnActionExecuting()<\/code> and <code>OnActionExecuted()<\/code>\u00a0 methods.\u00a0 Let&#8217;s take a look at the implementation of the <code>OnActionExecuting()<\/code> method to describe what is happening.<\/p>\n<pre class=\"lang:c# theme:vs2012\">\t\tpublic override void OnActionExecuting(ActionExecutingContext filterContext)\n\t{\n\t\u00a0\u00a0\u00a0 \/\/ First thing is to check if performance is enabled globally.\u00a0 If not, return\n\t\u00a0\u00a0\u00a0 if ( ConfigInfo.Value.PerformanceEnabled == false)\n\t\u00a0\u00a0\u00a0 {\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return;\n\t\u00a0\u00a0\u00a0 }\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \n\t\u00a0\u00a0\u00a0 \/\/ Second thing, check if performance tracking has been turned off for this action\n\t\u00a0\u00a0\u00a0 \/\/ If the DoNotTrackAttribute is present, then return\n\t\u00a0\u00a0\u00a0 ActionDescriptor actionDescriptor = filterContext.ActionDescriptor;\n\t\u00a0\u00a0\u00a0 if (actionDescriptor.GetCustomAttributes(typeof(DoNotTrackPerformanceAttribute), \n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 true).Length &gt; 0 || \u00a0\u00a0\u00a0\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 actionDescriptor.ControllerDescriptor.GetCustomAttributes( \n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 typeof(DoNotTrackPerformanceAttribute), true).Length &gt; 0)\n\t\u00a0\u00a0\u00a0 {\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return;\n\t\u00a0\u00a0\u00a0 }\n\t\u00a0\n\t\u00a0\u00a0\u00a0 \/\/ ActionInfo encapsulates all the info about the action being invoked\n\t\u00a0\u00a0\u00a0 ActionInfo info = this.CreateActionInfo(filterContext);\n\t\u00a0\n\t\u00a0\u00a0\u00a0 \/\/ PerformanceTracker is the object that tracks performance and is attached to the request\n\t\u00a0\u00a0\u00a0 PerformanceTracker tracker = new PerformanceTracker(info);\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \n\t\u00a0\u00a0\u00a0 \/\/ Store this on the request\n\t\u00a0\u00a0\u00a0 String contextKey = this.GetUniqueContextKey(filterContext.ActionDescriptor.UniqueId);\n\t\u00a0\u00a0\u00a0 HttpContext.Current.Items.Add(contextKey, tracker);\n\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \n\t\u00a0\u00a0\u00a0 \/\/ Process the action start - this is what starts the timer and increments any\n\t\u00a0\u00a0\u00a0 \/\/ required counters before the action executes\n\t\u00a0\u00a0\u00a0 tracker.ProcessActionStart();\n\t}\n\t\t<\/pre>\n<p>The first responsibility of this method is to determine whether performance is being tracked on this controller action. First, it checks a Singleton class called<code> ConfigInfo<\/code> to see if performance is enabled on an application-wide basis. This call will return <code> false<\/code> if the <code> ConfigInfo<\/code> class is not able to find the name of the performance counter category in the <code> Web.Config<\/code> file or if the category name that is present has not been created on the server. In these cases, the application is not properly configured to track performance, so there is no point in continuing. In this case, \u00a0this method simply returns.<\/p>\n<p>While it is possible to apply the <code> MvcPerformance<\/code> attribute to individual controllers and actions, it is more likely that the filter will be registered globally in the <code> GlobalFilterCollection<\/code>, thereby applying to all actions throughout an application. A developer may want to have some actions opt out of having their performance tracked. The solution to this problem was inspired by an MSDN Blog post by Rick Anderson (<a href=\"http:\/\/blogs.msdn.com\/b\/rickandy\/archive\/2011\/05\/02\/securing-your-asp-net-mvc-3-application.aspx\">Securing your ASP.NET MVC 3 application<\/a>). A second Attribute class is defined,<code> DoNotTrackPerformanceAttribute<\/code>. The code above checks to see if this second attribute has been placed on either the action or the controller. If it has, then this action will be excluded from performance tracking. In this way, an application developer has very fine grain control over what methods are included and excluded for performance tracking.<\/p>\n<p>If both of the checks described above pass, then performance should be tracked on this controller action.\u00a0 A helper method is used to create an <code>ActionInfo<\/code> object, which is an object that encapsulates all of the information about the controller action that is being called. \u00a0The code then creates the <code>PerformanceTracker<\/code> object, which is the object with primary responsibility for tracking the performance of the controller action.\u00a0 Each request that performance is being measured for will have an associated <code>PerformanceTracker<\/code> object, and the associated <code>PerformanceTracker<\/code> object will need to be retrieved again in the <code>OnActionExecuted()<\/code> method after the controller action completes.\u00a0 For this reason, the <code>PerformanceTracker<\/code> object is stored in the Items dictionary of the current <code>HttpContext <\/code>object.\u00a0 The Items dictionary on <code>HttpContext<\/code> is designed to be used when data needs to be shared between different Http handlers and modules during a request.\u00a0 The key that is used is based on the full name of the attribute type and the ASP.NET generated unique id for the method.\u00a0 By combining these elements together, we should be safe from any key collisions with other modules that use the Items dictionary.\u00a0 Finally, the <code>ProcessActionStart()<\/code> method of the <code>PerformanceTracker<\/code> object is called.<\/p>\n<p>The code for the <code>ProcessActionStart()<\/code> method of the <code>PerformanceTracker<\/code> is shown below.<\/p>\n<pre class=\"lang:c# theme:vs2012\">\t\t\t\t\t\tinternal void ProcessActionStart()\n\t\t\t{\n\t\t\t\u00a0\u00a0\u00a0 try\n\t\t\t\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Use the factory class to get all of the performance metrics that are being tracked\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ for MVC Actions\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.performanceMetrics = PerformanceMetricFactory.GetPerformanceMetrics(actionInfo);\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Iterate through each metric and call the OnActionStart() method\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Start off a task to do this so it can it does not block\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Task t = Task.Factory.StartNew(() =&gt;\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 foreach (PerformanceMetricBase m in this.performanceMetrics)\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0{\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 m.OnActionStart();\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 });\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.stopwatch = Stopwatch.StartNew();\n\t\t\t\u00a0\u00a0\u00a0 }\n\t\t\t\u00a0\u00a0\u00a0 catch (Exception ex)\n\t\t\t\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 String message = String.Format(\"Exception {0} occurred \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PerformanceTracker.ProcessActionStart().\u00a0 Message {1}\\nStackTrace {0}\",\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ex.GetType().FullName, ex.Message, ex.StackTrace);\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Trace.WriteLine(message);\n\t\t\t\u00a0\u00a0\u00a0 }\n\t\t\t}\n\t\t\t\u00a0\n\t\t\t\t\t\t<\/pre>\n<p>The first thing to note about the code is that everything is wrapped within a try-catch block.\u00a0 If an exception is thrown, we want the exception to be logged to the tracing framework (or another logging framework) so we can troubleshoot why we are not receiving any data.\u00a0 We do not want the exception to bubble up and cause an application error, making this one of the few times where it is probably the right choice to swallow an exception.\u00a0 The code then uses the<code>PerformanceMetricFactory <\/code>class to get a List of <code>PerformanceMetricBase<\/code> objects.\u00a0 Each<code> PerformanceMetricBase<\/code> object encapsulates the logic needed to update one or more performance counters.\u00a0 The code iterates through each one of these objects and calls its <code>OnActionStart()<\/code> method.\u00a0 As its name implies, <code>OnActionStart() <\/code>gives a <code>PerformanceMetricBase<\/code> object the opportunity to update any performance counters at the beginning of the controller action call, such as incrementing the counter that tracks how many calls are in progress.<\/p>\n<p>Finally, a <code>Stopwatch<\/code> object from the <code>System.Diagnostics <\/code>namespace is created and started to track the amount of elapsed time it takes to execute the action method.\u00a0 The reference to the <code>Stopwatch<\/code> object, as well as a reference to the List of <code>PerformanceMetricBase objects<\/code> are held as member variables within the <code>PerformanceTracker<\/code> object so they are available for when the <code>OnActionExecuted()<\/code> method is invoked.<\/p>\n<p>After the controller action finishes, the <code>OnActionExecuted()<\/code> method of the <code>MvcPerformanceAttribute<\/code> class will be executed.\u00a0 The code for this method is shown below.<\/p>\n<pre class=\"lang:c# theme:vs2012\">\t\t\t\t\t\tpublic override void OnActionExecuted(ActionExecutedContext filterContext)\n\t\t\t{\n\t\t\t\u00a0\u00a0\u00a0 \/\/ This is the unique key the PerformanceTracker object would be stored under\n\t\t\t\u00a0\u00a0\u00a0 String contextKey = this.GetUniqueContextKey(filterContext.ActionDescriptor.UniqueId);\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0 \u00a0\/\/ Check if there is an object on the request.\u00a0 If not, must not be tracking performance\n\t\t\t\u00a0\u00a0\u00a0 \/\/ for this action, so just go ahead and return\n\t\t\t\u00a0\u00a0\u00a0 if (HttpContext.Current.Items.Contains(contextKey) == false)\n\t\t\t\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return;\n\t\t\t\u00a0\u00a0\u00a0 }\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 \/\/ If we are here, we are tracking performance.\u00a0 Extract the object from the request and\n\t\t\t\u00a0\u00a0\u00a0 \/\/ call ProcessActionComplete to stop the stopwatch and update the performance metrics\n\t\t\t\u00a0\u00a0\u00a0 PerformanceTracker tracker = HttpContext.Current.Items[contextKey] as PerformanceTracker;\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 if (tracker != null)\n\t\t\t\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 bool exceptionThrown = (filterContext.Exception != null);\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 tracker.ProcessActionComplete(exceptionThrown);\n\t\t\t\u00a0\u00a0\u00a0 }\n\t\t\t\u00a0}\n\t\t\t\u00a0\n\t\t\t<\/pre>\n<p>The first responsibility of this method is the determine if performance is being tracked on controller action.\u00a0 It does so by checking the Items dictionary on the <code>HttpContext<\/code> object.\u00a0 The presence of a PerformanceTracker object indicates performance is being tracked while the absence of the object in the dictionary indicates performance\u00a0 is not being tracked.\u00a0 If performance is being tracked, this method gets a reference to the <code>PerformanceTracker<\/code> object and calls <code>ProcessActionComplete()<\/code>.\u00a0 Passed to the <code>ProcessActionComplete()<\/code> method is a flag of whether an unhandled exception occurred during the processing of this controller action.\u00a0 This value of this flag is determined by examining the Exception property in the <code>ActionExecutedContext<\/code> object passed to this method.<\/p>\n<p>The code for <code>ProcessActionComplete()<\/code> is shown below.\u00a0<\/p>\n<pre class=\"lang:c# theme:vs2012\">\t\t\t\t\t\tinternal void ProcessActionComplete(bool unhandledExceptionFlag)\n\t\t\t{\n\t\t\t\u00a0\u00a0\u00a0 try\n\t\t\t\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Stop the stopwatch\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.stopwatch.Stop();\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Iterate through each metric and call the OnActionComplete() method\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Start off a task to do this so it can it does not block \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Task t = Task.Factory.StartNew(() =&gt;\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 foreach (PerformanceMetricBase m in this.performanceMetrics)\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 m.OnActionComplete(this.stopwatch.ElapsedTicks, unhandledExceptionFlag);\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 });\n\t\t\t\u00a0\u00a0\u00a0 }\n\t\t\t\u00a0\u00a0\u00a0 catch (Exception ex)\n\t\t\t\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 String message = String.Format(\"Exception {0} occurred \n\t\t\t\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0PerformanceTracker.ProcessActionComplete().\u00a0 Message {1}\\nStackTrace {0}\",\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ex.GetType().FullName, ex.Message, ex.StackTrace);\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Trace.WriteLine(message);\n\t\t\t\u00a0\u00a0\u00a0 }\n\t\t\t}\n\t\t\t\t\t\t<\/pre>\n<p>As with the <code>ProcessActionStart()<\/code> method, all of the code is wrapped within a try catch block to prevent exceptions in the performance framework from causing an error in the application.\u00a0 This method first calls the <code>Stop()<\/code> method on the stopwatch and then iterates through each of the <code>PerformanceMetricBase<\/code> objects, calling their <code>OnActionComplete()<\/code> method.\u00a0 There are two arguments passed to <code>OnActionComplete()<\/code>.\u00a0 One is the flag of whether or not an unhandled exception occurred while processing the controller action.\u00a0 The other is the number of elapsed ticks from the stopwatch object which represents the elapsed time the controller action took to be executed.\u00a0 This value is passed as the number of ticks because it is the number of ticks that is required by the average timer \u00a0performance counter, which will give us the average time a method took to execute over a period of time.\u00a0 If other <code>PerformanceMetricBase <\/code>objects need this value in milliseconds, it can be easily converted to milliseconds. \u00a0Finally, as shown above, the <code>OnActionComplete()<\/code> methods of the <code>PerformanceMetricBase<\/code> objects are run in a separate task in order to minimize any impact on page response time.<\/p>\n<h2>PerformanceMetricBase objects and the PerformanceMetricFactory<\/h2>\n<p>The task of updating the actual performance counters is delegated to objects that extend from the <code>PerformanceMetricBase<\/code> class.\u00a0 These objects serve as an intermediary between the<code>PerformanceTracker<\/code> object<code><\/code>and any performance counters that need to be updated.\u00a0 While it would be possible to write the <code>PerformanceTracker <\/code>class to directly update the necessary performance counters, factoring this code out into a separate set of objects allows the <code>PerformanceTracker<\/code> object to focus on managing the entire process of measuring performance on the controller action and leave the details of how to update the counters to the PerformanceMetricBase objects.\u00a0 This results in a more loosely couple design and allows for future extension.\u00a0 If we ever want to add additional performance metrics, this can be done by simply writing a new class that extends <code>PerformanceMetricBase<\/code> and leaves the code in <code>PerformanceTracker<\/code> undisturbed.<\/p>\n<p>Each child class that extends <code>PerformanceMetricBase<\/code> is responsible for updating the values that correspond to one of custom performance counters defined in the table at the beginning of this article.\u00a0 As such, each of these classes will contain a member variable holding a reference to the <code>System.Diagnostics.PerformanceCounter<\/code> object(s) they are responsible for updating.\u00a0 Generally this is a single <code>System. \u00adDiagnostics. \u00adPerformance \u00adCounter<\/code> object. \u00a0However, some counters require not just the target counter to be updated, but also a base counter.\u00a0 Internally, the performance counter system uses both of these values to calculate the actual result.\u00a0 A counter that measures the average time of an operation is such an example.\u00a0 Therefore, the <code>AverageCall \u00adTimeMetric<\/code> class contains member variables for both the timer counter and the base counter.<\/p>\n<p>The <code>PerformanceMetricBase<\/code> provides two virtual methods,\u00a0 <code>OnActionStart()<\/code> and <code>OnAction \u00adComplete() <\/code><code><\/code>where child classes are able to perform updates to performance counters.\u00a0 Child implementations need to override at least one or can override both of these methods.\u00a0 The <code>CallsInProgressMetric<\/code> is an example of a class that overrides both methods by incrementing, and then decrementing, the &#8220;Calls in Progress&#8221; counter.\u00a0 Updating of a performance counter is accomplished by the use of the <code>Increment(),<\/code><code>IncrementBy()<\/code><code>,<\/code> <code>Decrement()<\/code> methods and the <code>RawValue <\/code><code><\/code>property on the <code>PerformanceCounter<\/code> class.\u00a0 The <code>Increment(),<\/code><code>IncrementBy()<\/code> and <code>Decrement()<\/code><code><\/code>methods all use an internal locking process to keep these methods safe for multithreaded and multiprocess scenarios.\u00a0 The <code>RawValue <\/code>property does not contain any mechanisms to ensure thread safety, so it is only appropriate to use where it is OK to only have access to the last update.\u00a0 An example of this is the &#8220;Last Call Elapsed Time&#8221; counter, which is intended to just overwrite whatever value was in the counter previously.<\/p>\n<pre class=\"lang:c# theme:vs2012\">\t\t\t \/\/\/ &lt;summary&gt;\n\t\t\t \/\/\/ Performance Metric that updates the counters that track the average time a method took\n\t\t\t \/\/\/ &lt;\/summary&gt;\n\t\t\t public class AverageCallTimeMetric : PerformanceMetricBase\n\t\t\t{\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 public AverageCallTimeMetric(ActionInfo info)\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 : base(info)\n\t\t\t\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 String categoryName = this.actionInfo.PerformaneCounterCategory;\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 String instanceName = this.actionInfo.InstanceName;\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.averageTimeCounter\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = this.InitializeCounter(categoryName, COUNTER_NAME, instanceName);\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.baseCounter\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = this.InitializeCounter(categoryName, BASE_COUNTER_NAME, instanceName);\n\t\t\t\u00a0\u00a0\u00a0 }\n\t\t\t\u00a0\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;summary&gt;\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ Constant defining the name of the average time counter\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;\/summary&gt;\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;remarks&gt;\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ This is the counter name that will show up in perfmon\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;\/remarks&gt;\n\t\t\t\u00a0\u00a0\u00a0 public const String COUNTER_NAME = \"Average Time per Call\";\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;summary&gt;\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ Constant defining the name of the base counter to use\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;\/summary&gt;\n\t\t\t\u00a0\u00a0\u00a0 public const String BASE_COUNTER_NAME = \"Average Time per Call Base\";\n\t\t\t\u00a0\n\t\t\t\u00a0\n\t\t\t \u00a0\u00a0\u00a0 #region Member Variables\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 private PerformanceCounter averageTimeCounter;\n\t\t\t\u00a0\u00a0\u00a0 private PerformanceCounter baseCounter;\n\t\t\t\u00a0\n\t\t\t \u00a0\u00a0\u00a0 #endregion\n\t\t\t\u00a0\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;summary&gt;\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ Method called by the custom action filter after the action completes\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;\/summary&gt;\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;remarks&gt;\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ This method increments the Average Time per Call counter by the number of ticks\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ the action took to complete and the base counter is incremented by 1 (this is\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ done in the PerfCounterUtil.IncrementTimer() method).\u00a0 \n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;\/remarks&gt;\n\t\t\t\u00a0\u00a0\u00a0 \/\/\/ &lt;param id=\"elapsedTicks\"\"&gt;A long of the number of ticks it took to complete the action&lt;\/param&gt;\n\t\t\t\u00a0\u00a0\u00a0 public override void OnActionComplete(long elapsedTicks, bool exceptionThrown)\n\t\t\t\u00a0\u00a0\u00a0 {\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.averageTimeCounter.IncrementBy(elapsedTicks);\n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.baseCounter.Increment();\n\t\t\t\u00a0\u00a0\u00a0 }\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 \/\/ Dispose method not shown for brevity\n\t\t\t}\n\t\t\t<\/pre>\n<p>The <code>PerformanceTracker<\/code> object uses the <code>GetPerformanceMetrics()<\/code> method of the <code>Performance \u00adMetric \u00adFactory<\/code> class to get a list of the performance metric objects that will be updated for a controller action.\u00a0 The <code>GetPerformanceMetrics()<\/code> is passed an <code>ActionInfo<\/code> object, which it used as a key to look for the corresponding <code>PerformanceMetricBase<\/code> objects in an internal dictionary.\u00a0 If the <code>ActionInfo<\/code> object is found in the dictionary, then the a reference to the existing <code>PerformanceMetricBase<\/code> objects is returned. \u00a0If not, <code>PerformanceMetricFactory<\/code> will create the appropriate <code>PerformanceMetricBase<\/code> objects, store a reference to the collection and return them. \u00a0This means any time a particular controller action is invoked, the same set of <code>Performance \u00adMetric \u00adBase<\/code> objects are used.\u00a0 It also makes clear what the true role of a <code>Performance \u00adMetric \u00adBase<\/code> object is, and that is encapsulate access to a custom performance counter for a given controller action.<\/p>\n<h2>What About Web API?<\/h2>\n<p>No discussion would be complete without mentioning how the same strategy can be used to track the performance of Web API controller actions.\u00a0 As it turns out, the Web API actions are processed in a very similar fashion as calls to ASP.NET MVC actions. \u00a0One primary difference is that custom action filters extend from the <code>System.Web. \u00adHttp.Action \u00adFilterAttribute<\/code> class rather than the <code>System.Web. \u00adMvc.Action \u00adFilterAttribute<\/code> class.\u00a0 As such, the <code>OnActionExecuting()<\/code> and <code>OnActionExectured()<\/code> methods are also passed slightly different objects.\u00a0 Therefore, the solution defines a second performance attribute, <code>WebApi \u00adPerformance \u00adAttribute<\/code> that can be used to track performance of Web API actions in the same way.\u00a0 This attribute will pass a String to the <code>ActionInfo<\/code> class to indicate that the action being tracked is a Web API action, but otherwise it functions exactly the same way \u00a0as its MVC counterpart.<\/p>\n<p>The other difference is that the <code>HttpActionContext<\/code> and <code>HttpActionExecutedContext<\/code> objects passed to <code>OnActionExecuting()<\/code> and <code>OnActionExecuted<\/code><i>() <\/i><code><\/code>contain a property named Request, which exposes an <code>HttpRequestMessage<\/code> object representing the request.\u00a0 This object exposes a dictionary through its Properties object, and in this case, it is this dictionary used to store the reference to the <code>PerformanceTracker<\/code> object during the request lifecycle.<\/p>\n<p>There is one special case to be aware of when using Web API.\u00a0 If a controller action throws an exception of the Type <code>HttpResponseException,<\/code> then the <code>OnActionExecuted<\/code><code>()<\/code> method of the <code>WebApi \u00adPerformance \u00adAttribute<\/code> will still be executed and process as normal.\u00a0 However, the<code>Exception<\/code> property of the <code>HttpAction \u00adExecuted \u00adContext<\/code> object passed to the <code>OnActionExecuted()<\/code> method will be <code>null<\/code>.\u00a0 Therefore, any performance-tracking that depends on a reference to the exception will not work.\u00a0 In this solution, that means that neither the <code>TotalExceptions \u00adThrownMetric<\/code> or <code>DeltaExceptionsThrownMetric<\/code> will increment their respective counters in this case.<\/p>\n<h2>Integrating Performance Tracking Into a Web Application<\/h2>\n<p>One of the design criteria laid out at the beginning of this article was to design a solution which was easy to implement across an application.\u00a0 By using a custom action filter, we can add the filter to all actions in an application by simply adding the action to the<code>GlobalFiltersCollection<\/code> on application startup in the <code>Global.asax <\/code>file.\u00a0 The code below shows such an implementation where the <code>MvcPerformance<\/code> filter is added just after the <code>HandleError<\/code> attribute.<\/p>\n<pre class=\"lang:c# theme:vs2012\">\t\t\t\u00a0\n\t\t\tpublic static void RegisterGlobalFilters(GlobalFilterCollection filters)\n\t\t\t{\n\t\t\t\u00a0\u00a0\u00a0 filters.Add(new HandleErrorAttribute());\n\t\t\t\u00a0\u00a0\u00a0 filters.Add(new MvcPerformanceAttribute());\n\t\t\t}\n\t\t\t\t\t\t<\/pre>\n<p>If you do not wish to apply the attribute globally, you can also decorate actions or controllers of interest with the appropriate attribute.<\/p>\n<pre class=\"lang:c# theme:vs2012\">\t\t\t\t\u00a0\n\t\t\t[MvcPerformance]\n\t\t\tpublic ActionResult Index()\n\t\t\t{\n\t\t\t\u00a0\u00a0\u00a0 \/\/ Get most popular albums\n\t\t\t\u00a0\u00a0\u00a0 var albums = GetTopSellingAlbums(5);\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 return View(albums);\n\t\t\t}\n\t\t\t\u00a0\n\t\t\t<\/pre>\n<p>If performance is being tracked at a global level, you can use the <code>DoNotTrackPerformanceAttribute<\/code> to opt out of performance tracking by decorating the action in a similar fashion<\/p>\n<pre class=\"lang:c# theme:vs2012\">\t\t\t\t\t\t[DoNotTrackPerformance]\n\t\t\tpublic ActionResult Index()\n\t\t\t{\n\t\t\t\u00a0\u00a0\u00a0 \/\/ Get most popular albums\n\t\t\t\u00a0\u00a0\u00a0 var albums = GetTopSellingAlbums(5);\n\t\t\t\u00a0\n\t\t\t\u00a0\u00a0\u00a0 return View(albums);\n\t\t\t}\n\t\t\t\t\t\t<\/pre>\n<p>Once the application is redeployed, you can check that data is being collected by using Windows <code>perfmon<\/code> and selecting the counters of interest. (click on the image to see it full-size)<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1889-clip_image004-630x384.jpg\" alt=\"1889-clip_image004-630x384.jpg\" width=\"630\" height=\"384\" \/><\/figure>\n<p class=\"illustration\">\u00a0<\/p>\n<h2>Summary<\/h2>\n<p>By using custom action filters and performance counters, any ASP.NET MVC or Web API application can be instrumented to track performance. The solution is easy to add to any MVC-based web application, and the code that tracks performance is cleanly separated from the application logic. By using performance counters, the data can be easily consumed by a variety of tools to support reporting and alerting needs. Because it is so simple to integrate performance-tracking into your MVC applications, you will always have data to guide your tuning efforts and diagnose performance issues as they arise.<\/p>\n<h2>References and Further Reading<\/h2>\n<ul class=\"reference-list\">\n<li>An Introduction to Performance Counters by Michael Groeger, <a href=\"http:\/\/www.codeproject.com\/Articles\/8590\/An-Introduction-To-Performance-Counters\">http:\/\/www.codeproject.com\/Articles\/8590\/An-Introduction-To-Performance-Counters<\/a><\/li>\n<li>Action Filtering in ASP.NET Applications, <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd410209(v=vs.100).aspx\">http:\/\/msdn.microsoft.com\/en-us\/library\/dd410209(v=vs.100).aspx<\/a><\/li>\n<li>MVC Music Store Tutorial by Jon Galloway, <a href=\"http:\/\/www.asp.net\/mvc\/tutorials\/mvc-music-store\">http:\/\/www.asp.net\/mvc\/tutorials\/mvc-music-store<\/a><\/li>\n<li>MVC Music Store Code Download, <a href=\"http:\/\/mvcmusicstore.codeplex.com\/\">http:\/\/mvcmusicstore.codeplex.com\/<\/a><\/li>\n<li>Securing Your MVC3 Application by Rick Anderson, <a href=\"http:\/\/blogs.msdn.com\/b\/rickandy\/archive\/2011\/05\/02\/securing-your-asp-net-mvc-3-application.aspx\">http:\/\/blogs.msdn.com\/b\/rickandy\/archive\/2011\/05\/02\/securing-your-asp-net-mvc-3-application.aspx<\/a><\/li>\n<\/ul>\n<\/div>\n\n\n\n<section id=\"my-first-block-block_c78f869289a3b15dc17b125cb3c99a57\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Discover how Redgate can help you: Simple Talk is brought to you by Redgate Software\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>","protected":false},"excerpt":{"rendered":"<p>Building performance monitoring into ASP.NET MVC applications &#8211; adding custom metrics, using Windows performance counters for request timing and throughput, and integrating Application Insights for production telemetry. With code examples for action-filter-based instrumentation.&hellip;<\/p>\n","protected":false},"author":49177,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[4143,4156,5166,4206],"coauthors":[11323],"class_list":["post-1715","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-asp","tag-mvc","tag-performance"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1715","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/49177"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1715"}],"version-history":[{"count":8,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1715\/revisions"}],"predecessor-version":[{"id":110980,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1715\/revisions\/110980"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1715"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1715"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1715"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1715"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}