Version scope

This guide targets Umbraco 8 on ASP.NET Web API / .NET Framework. Current Umbraco versions use ASP.NET Core and a different request pipeline.

Why Track Slow API Actions?

Slow API actions can affect page rendering, integrations, back-office tools, scheduled jobs, and any client that depends on the endpoint. The first useful step is usually not optimization itself, but finding which actions consistently exceed an acceptable execution time.

Targeted timing gives you a simple way to:

  • Spot delays: identify controller actions that regularly exceed your threshold.

  • Focus investigation: narrow profiling and database analysis to the endpoints that actually need attention.

  • Monitor integrations: detect regressions in API actions used by external clients.

  • Collect evidence: log method, URI, and elapsed time before changing the implementation.

The Concept: Measuring API Action Execution

The implementation uses a custom ActionFilterAttribute. A Stopwatch starts before the controller action executes and stops after the action finishes. If the elapsed time exceeds a configurable threshold, the filter writes a warning to the Umbraco log.

  1. Start a Stopwatch before the API action.

  2. Store it with the current Web API request.

  3. Stop it after the action has executed.

  4. Compare ElapsedMilliseconds with the configured threshold.

  5. Log only actions that exceed that threshold.

Small overhead, not zero overhead

starting a stopwatch, storing request state, comparing a threshold, and occasionally writing a log entry all have a cost. For targeted diagnostics this overhead is usually modest, but it should not be described as free.

Step 1: Implement the API Performance Tracking Filter

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Umbraco.Core.Composing;

public sealed class UmbracoApiPerformanceTrackingAttribute : ActionFilterAttribute
{
    private const string StopwatchKey = "UmbracoApiPerformanceTracking.Stopwatch";

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (ApiPerformanceSettings.ProfilingEnabled)
        {
            actionContext.Request.Properties[StopwatchKey] = Stopwatch.StartNew();
        }

        base.OnActionExecuting(actionContext);
    }

    public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
    {
        if (ApiPerformanceSettings.ProfilingEnabled &&
            actionExecutedContext.Request.Properties.TryGetValue(StopwatchKey, out var value) && value is Stopwatch stopwatch)
        {
            stopwatch.Stop();

            if (stopwatch.ElapsedMilliseconds >= ApiPerformanceSettings.SlowResponseThresholdMilliseconds)
            {
                Current.Logger.Warn(
                    typeof(UmbracoApiPerformanceTrackingAttribute),
                    $"SLOW API ACTION: {stopwatch.ElapsedMilliseconds} ms - " +
                    $"{actionExecutedContext.Request.Method} " +
                    $"{actionExecutedContext.Request.RequestUri}");
            }
        }

        return base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
    }
}

The warning includes the measured execution time, HTTP method, and request URI. The filter also checks that the stored object exists before attempting to stop it.

Step 2: Apply the Filter

Add the attribute to an entire UmbracoApiController when all of its actions should be measured:

[UmbracoApiPerformanceTracking]
public class SlowUmbracoApiController : UmbracoApiController
{
   public IHttpActionResult SlowEndpointA(string id)
   {
      return Json("Hello from Endpoint A");
   }

   public IHttpActionResult SlowEndpointB(string id)
   {
      return Json("Hello from Endpoint B");
   }
}

You can also apply [UmbracoApiPerformanceTracking] to a single API action. That is often the better option when you are investigating one known slow endpoint and do not need timing data for the rest of the controller.

Step 3: Configure Logging for Production

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="ApiPerformance:Enabled" value="true" />
        <add key="ApiPerformance:SlowResponseThresholdMilliseconds" value="1000" />

        <!-- Example only: choose the level required by your environment -->
        <add key="serilog:minimum-level" value="Warning" />
    </appSettings>
</configuration>

A threshold of 1000 means an action is logged when its measured execution time is at least one second. Choose the value based on the endpoint and its performance expectations rather than treating one second as a universal definition of "slow."

Do not change the global log level blindly

Setting the minimum level to Warning also suppresses normal Information events from the rest of the application. Use the level that matches your production diagnostics and retention strategy.

What This Filter Measures and What It Does Not

This technique measures the time between the action filter's pre-action and post-action callbacks. It is therefore useful for finding slow controller actions, but it is not a complete end-to-end HTTP response timer.

The measurement can exclude work that happens elsewhere in the request lifecycle, such as:

  • earlier message handlers or routing;

  • some result execution or serialization work;

  • server or proxy queues;

  • network latency between the server and the client.

If you need full request timing, use instrumentation at a higher point in the ASP.NET request pipeline or an application performance monitoring tool. For diagnosing individual Umbraco API actions, however, the action filter remains a simple and focused option.

Official References

Conclusion

A custom action filter gives an Umbraco 8 project a lightweight way to identify controller actions that exceed a defined execution-time threshold. Start a Stopwatch, store it with the current request, compare the elapsed milliseconds after the action, and log only the slow cases.

Treat the result as action execution timing, not full request latency. Once an endpoint is identified as slow, use database profiling, application tracing, external dependency metrics, or broader request instrumentation to find the actual bottleneck.