Adding a delay to ASP.NET Core Web API methods to simulate slow or erratic networks

I recently commented on a tweet by @jongalloway, saying:

It got me thinking about ways to do this in ASP.NET Core, other than the simple and manual process of whacking a breakpoint at the start of each action and leaving it spinning for a while or adding a delay using a network monitoring tool. The answer I came up with was filters, specifically action filters. According to the documentation:

Filters in ASP.NET Core MVC allow you to run code before or after specific stages in the request processing pipeline.

This gave me exactly what I want, a way to run code (specifically some code to introduce a delay) at a specific stage of the request. For the purposes of the exercise the when didn't particularly matter to me, though if you've got server side code that relies on being hit within a specific time-frame, e.g. within a second of the client-side request being initiated, you may need to take this into account.

The filter I put together actually ended up being pretty simple:

Filter code:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using System.Threading.Tasks;

namespace CoreDelayingTactics.Filters
{
    public class DelayFilter : IAsyncActionFilter
    {
        private int _delayInMs;
        public DelayFilter(IConfiguration configuration)
        {
            _delayInMs = configuration.GetValue<int>("ApiDelayDuration", 0);
        }

        async Task IAsyncActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            await Task.Delay(_delayInMs);
            await next();
        }
    }
}

There are two things to point out about the filter; firstly, it's not production code and should really have logging (you want something in your logs so you can see when it's active in production, right?), possibly a skip past the call to Task.Delay if the value is 0, validation of the delay value, perhaps even a #if DEBUG so that the call to Task.Delay gets compiled out in release builds. Secondly, it's using Task.Delay instead of the ubiquitous Thread.Sleep. Pretty much everything I've read says that using Thread.Sleep in async code is a bad idea (to be fair it's usually a sign that you're hacking your way round a problem even in non-async code) but Task.Delay is far more palatable. Even in code like this that isn't "production" code it's important to do things the right way so as to ensure that something isn't later taken as a "well, it's done here so it must be fine" example.

Testing it

I've used the standard ASP.NET Core template and added the filter by wiring it in through the ConfigureServices method in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
        {
            options.Filters.Add(typeof(DelayFilter));
        })
        .AddNewtonsoftJson();
}

With that done, the last thing to do is add a setting to appsettings.json so the filter actually introduces a delay:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ApiDelayDuration":  "5000"
}

Now to test it, by hitting one of the sample actions that's included in the template (https://localhost:44363/api/values), which shows this result in Fiddler:

ACTUAL PERFORMANCE
--------------
ClientConnected:	06:29:08.372
ClientBeginRequest:	06:29:08.449
GotRequestHeaders:	06:29:08.449
ClientDoneRequest:	06:29:08.449
Determine Gateway:	0ms
DNS Lookup: 		0ms
TCP/IP Connect:	0ms
HTTPS Handshake:	0ms
ServerConnected:	06:29:08.409
FiddlerBeginRequest:	06:29:08.449
ServerGotRequest:	06:29:08.450
ServerBeginResponse:	06:29:13.728
GotResponseHeaders:	06:29:13.728
ServerDoneResponse:	06:29:13.793
ClientBeginResponse:	06:29:13.793
ClientDoneResponse:	06:29:13.794

	Overall Elapsed:	0:00:05.344

That's all there is to it. Hopefully this'll come in handy for you.

About Rob

I've been interested in computing since the day my Dad purchased his first business PC (an Amstrad PC 1640 for anyone interested) which introduced me to MS-DOS batch programming and BASIC.

My skillset has matured somewhat since then, which you'll probably see from the posts here. You can read a bit more about me on the about page of the site, or check out some of the other posts on my areas of interest.

No Comments

Add a Comment