DevProxy: How to Test API Rate Limiting and Throttling in C# Development

Aug 10, 2025 min read

The Problem: When Parallel Programming Backfires

If you’ve ever optimized your C# API calls with parallel programming, you might know this story.

Your client wants faster performance. You run multiple API requests in parallel. Locally, everything flies — especially late at night when traffic is low. But the next morning, on the final pre-deployment test, disaster strikes.

Random errors. Requests delayed for 45 seconds.
Microsoft Graph or SharePoint has slammed you with throttling and rate limits. Your blazing-fast local solution crumbles under production-like conditions.


TL;DR

  • Parallel programming can trigger throttling and rate limiting in production, even if it works locally.
  • DevProxy lets you simulate throttling during development so you can fix issues early.
  • Use the sample configuration below to test Microsoft Graph and SharePoint API calls.
  • Follow Bert Jansen’s throttling guide for proven handling patterns.
  • Start testing early — not after problems appear.

How CRUD Operations Can Secretly Burn Through Your Limits — and How to Catch Them with DevProxy

Here’s the thing:
SharePoint Online charges “Resource Units” (RUs) for every request you make. Think of RUs as an invisible currency — every API call you send deducts from your allowance. Run out too fast, and throttling kicks in.

And those “simple” operations? They’re not as cheap as you think.

From Microsoft’s RU table:

Operation Type RU Cost What That Means
Single item query 1 RU Reading one specific list item
Multi-item query 2 RUs Listing children, filtering, sorting
Create / Update / Delete / Upload 2 RUs Per individual operation (bulk operations may be more efficient)
Permission expansion ($expand=permissions) 5 RUs Heavy permission lookups

Source: Microsoft’s official SharePoint throttling documentation

Permission expansions are just one type of “expensive” call. Another common scenario that can impact RU consumption is making multiple separate queries instead of using CAML joins — something I explored in my CAML join post.
While joining multiple lists in a single query is actually more efficient than separate calls, poorly structured queries or retrieving unnecessarily large datasets can still consume RUs quickly if not optimized properly.


Why This Sneaks Up on You

When coding locally, it’s easy to run a handful of queries without noticing.
But in production, with real concurrency and volume, these calls pile up in seconds.

Imagine:

  • 10 parallel updates (2 RUs each) = 20 RUs in one burst
  • Add a few joins/expansions or large list queries, and you’re suddenly burning through limits 5x faster.

How DevProxy Can Show You the Pain Before Production

Here’s where DevProxy shines.
If you configure it to simulate throttling based on these RU-heavy calls, you’ll see the impact locally:

  1. Enable throttling plugins in your devproxy.json (as shown earlier).
  2. Point urlsToWatch at your SharePoint endpoints.
  3. Run your CRUD-heavy code.

DevProxy will start throwing 429s (Too Many Requests) once your “fake” RU budget is exhausted — just like SharePoint would in production.

The beautiful part?
You can crank the limits down during testing to make expensive patterns obvious. Even a single large query or unbatched Update will light up your logs.

Example DevProxy log when hitting a throttling simulation:

[Warning] Throttling triggered: 2 parallel Create calls exceeded RU limit (RateLimitingPlugin)
Retry after: 30 seconds

Pro tip:
Use DevProxy as a budget meter for your API calls. Treat every 2-RU and 5-RU operation as “spending big” — and redesign those spots before they become a production outage.

What is DevProxy?

DevProxy is an open-source HTTP/HTTPS proxy server that can simulate real-world network issues, including:

It’s free, open source, and designed for developers who want to catch performance bottlenecks before they reach production.


Installing DevProxy

Follow the official setup guide here:
DevProxy Installation Documentation


Basic Usage

Run DevProxy with the default configuration:

devproxy

It will begin intercepting all HTTP/HTTPS requests.

Tip: Start DevProxy before running your own code — otherwise, it won’t capture the traffic.


Simulating Microsoft Graph Throttling

Here’s how to configure DevProxy to reproduce Microsoft Graph and SharePoint throttling issues locally.

1. Create a devproxy.json configuration file

This is the exact configuration I used when testing my problematic parallel code:

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/rc.schema.json",
  "rate": 25,
  "plugins": [
    {
      "name": "RetryAfterPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/dev-proxy-plugins.dll"
    },
    {
      "name": "RateLimitingPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/dev-proxy-plugins.dll",
      "configSection": "rateLimitingPlugin"
    },
    {
      "name": "GraphRandomErrorPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/dev-proxy-plugins.dll",
      "configSection": "graphRandomErrorPlugin"
    }
  ],
  "urlsToWatch": [
    "https://graph.microsoft.com/v1.0/*",
    "https://graph.microsoft.com/beta/*",
    "https://graph.microsoft.us/v1.0/*",
    "https://graph.microsoft.us/beta/*",
    "https://dod-graph.microsoft.us/v1.0/*",
    "https://dod-graph.microsoft.us/beta/*",
    "https://microsoftgraph.chinacloudapi.cn/v1.0/*",
    "https://microsoftgraph.chinacloudapi.cn/beta/*",
    "https://*.sharepoint.*/*_api/web/GetClientSideComponents",
    "https://*.sharepoint.*/*_api/*",
    "https://*.sharepoint.*/*_vti_bin/*",
    "https://*.sharepoint-df.*/*_api/*",
    "https://*.sharepoint-df.*/*_vti_bin/*"
  ],
  "graphRandomErrorPlugin": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/graphrandomerrorplugin.schema.json",
    "allowedErrors": [
      429,
      503,
      504
    ]
  },
  "rateLimitingPlugin": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/ratelimitingplugin.schema.json",
    "costPerRequest": 2,
    "rateLimit": 120
  },
  "logLevel": "information",
  "newVersionNotification": "stable",
  "showSkipMessages": true,
  "showTimestamps": true
}

2. Start DevProxy with your configuration

devproxy --config-file devproxy.json

Example of Problematic Code

Here’s the snippet that caused my throttling nightmare. It uses Parallel.ForEachAsync to query SharePoint in bulk:

// ❌ This will likely trigger throttling in production
var customers = new ConcurrentBag<CustomerDTO>();

await Parallel.ForEachAsync(departmentIds, async (departmentId, token) => 
{
    var clonedContext = _clientContext.Clone(_clientContext.Url);

    var query = Camlex.Query()
        .ViewFields(new CustomerDTO().ViewFields().ToArray().Append("DepartmentId"))
        .LeftJoin(x => x["DepartmentLookup"].ForeignList(DEPARTMENT_LIST_GUID))
        .ProjectedField(x => x["DepartmentId"].List(DEPARTMENT_LIST_GUID).ShowField("ID"))
        .Where(x => x["DepartmentId"] == departmentId);

    var departmentCustomers = await SharePointService.GetItemsFromListByQuery<CustomerDTO>(
        CUSTOMER_LIST_GUID,
        clonedContext,
        query);

    if (departmentCustomers?.Any() == true)
    {
        foreach (var customer in departmentCustomers)
        {
            customers.Add(customer);
        }
    }
});

Why This Code Fails in Production

  1. No throttling safeguards — Multiple parallel requests overwhelm SharePoint.
  2. No retry logic — Requests fail instead of recovering.
  3. No request limiting — All departments are processed simultaneously.
  4. Silent failures — Errors are ignored without logging or fallback.

The Better Way

Instead of hard-coding fixes here, I recommend Bert Jansen’s detailed guide on throttling and rate limit handling:
Throttling & Rate Limit Handling Patterns

Key principles:

  • Limit concurrency with SemaphoreSlim.
  • Use exponential backoff for retries.
  • Monitor rate limit headers to adjust requests dynamically.
  • Handle errors explicitly to prevent silent failures.

Conclusion

DevProxy has become an essential tool in my Microsoft 365 development workflow. It helps me:

  • Catch throttling issues before production.
  • Test error handling and retry logic locally.
  • Deliver applications that can survive real-world API limits.

If I’d used DevProxy from the start, I could have avoided the last-minute throttling meltdown entirely.

💡 Pro tip: Make DevProxy part of your early development process, not your emergency toolkit.


Additional Resources

Jeppe Spanggaard

A passionate software developer. I love to build software that makes a difference!