<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dev Proxy Recipes on Jeppe Spanggaard - Software Developer | .NET, Azure &amp; Microsoft 365</title><link>https://jeppe-spanggaard.dk/tags/dev-proxy/</link><description>Recent content in Dev Proxy Recipes on Jeppe Spanggaard - Software Developer | .NET, Azure &amp; Microsoft 365</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Sat, 25 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jeppe-spanggaard.dk/tags/dev-proxy/index.xml" rel="self" type="application/rss+xml"/><item><title>One Big PnP Template or Many Small Ones?</title><link>https://jeppe-spanggaard.dk/blogs/pnp-template-sizing-resilience/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><author>Jeppe</author><guid>https://jeppe-spanggaard.dk/blogs/pnp-template-sizing-resilience/</guid><description>After testing with DevProxy, I found that monolithic PnP templates silently destroy your retry strategy. Here's what I learned.</description><content:encoded><![CDATA[<p>Every time I sit down to build a SharePoint provisioning engine, I hit the same dilemma: do I put everything into one big PnP template, or do I split it into many smaller ones — one per feature?</p>
<p>Both approaches work. Both have real trade-offs. And for a long time I did not have a strong opinion either way. Then I started testing with <a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview">Dev Proxy</a> and suddenly the answer became very clear to me.</p>
<h2 id="what-is-a-pnp-provisioning-template">What Is a PnP Provisioning Template?</h2>
<p>If you have not worked with PnP provisioning before: a PnP Provisioning Template is an XML (or JSON) file that describes the structure of a SharePoint site. It can contain:</p>
<ul>
<li><strong>Lists and libraries</strong> — custom columns, views, and settings</li>
<li><strong>Content types</strong> — site or list content types with their fields</li>
<li><strong>Pages and web parts</strong> — modern pages, hero sections, text blocks</li>
<li><strong>Navigation and branding</strong> — site navigation structure, themes, logos</li>
</ul>
<p>You apply a template to a site using the <a href="https://github.com/pnp/pnpframework">PnP Framework</a> library for .NET, which reads the file and provisions everything described in it. The library handles the order of operations, dependencies between objects, and a lot of edge cases you would rather not think about.</p>
<p>The typical entry point looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>web.ApplyProvisioningTemplate(template, applyInfo);
</span></span></code></pre></div><p>One call. One template. PnP handles the rest — until throttling enters the picture.</p>
<h2 id="the-monolith-approach">The Monolith Approach</h2>
<p>The simplest way to structure your provisioning is one template that contains everything. All lists, all content types, all pages, all navigation — in a single file.</p>
<p><strong>Pros:</strong></p>
<ul>
<li>✅ Simple to manage — one file, one version, one deployment artifact</li>
<li>✅ Easy to apply — a single <code>ApplyProvisioningTemplate</code> call</li>
<li>✅ PnP handles dependencies between objects internally</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>❌ All-or-nothing retry — if anything fails, you restart from the beginning</li>
<li>❌ Long templates take a long time to apply, which means long retries too</li>
<li>❌ Hard to reason about what failed and where</li>
</ul>
<p>The all-or-nothing behavior is the killer. SharePoint throttles aggressively under load, and <code>ApplyProvisioningTemplate</code> does not pick up where it left off. Under a sustained throttle — the kind where even 15 retries at the CSOM call level are not enough to get through — the template application eventually throws a <code>MaximumRetryAttemptedException</code> — a PnP-specific exception signalling that all retry attempts were used up. At that point your outer retry logic has no choice but to start the whole template over from the very first list.</p>
<h2 id="the-modular-approach">The Modular Approach</h2>
<p>The alternative is splitting your template into multiple smaller files, each representing a logical feature or concern. For example:</p>
<ul>
<li><code>01-lists-and-libraries.xml</code></li>
<li><code>02-content-types.xml</code></li>
<li><code>03-pages.xml</code></li>
<li><code>04-navigation.xml</code></li>
</ul>
<p>You apply them in sequence, and after each one completes successfully you record that fact in memory. If a template fails, you retry only from the failed template — not from the beginning.</p>
<p><strong>Pros:</strong></p>
<ul>
<li>✅ Granular retry — only the failed feature is re-applied</li>
<li>✅ Faster recovery from throttling</li>
<li>✅ Easier to reason about failures (&ldquo;pages failed, lists are fine&rdquo;)</li>
<li>✅ Easier to test individual features in isolation</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>❌ More files to manage and version</li>
<li>❌ Ordering matters — you need to think about dependencies</li>
<li>❌ Slightly more orchestration code on your end</li>
</ul>
<p>The trade-off is real. Modular means more moving parts. But for resilience, the payoff is significant.</p>
<h2 id="testing-with-devproxy--where-it-got-interesting">Testing With DevProxy — Where It Got Interesting</h2>
<p>I wanted to put both approaches through their paces under realistic throttling conditions. For that I used Dev Proxy with two plugins:</p>
<ul>
<li><strong>GenericRandomErrorPlugin</strong> — injects random 429 responses to simulate SharePoint throttling unpredictably</li>
<li><strong>RateLimitingPlugin</strong> — caps requests per second to trigger organic throttling at volume</li>
</ul>
<p>With these configured, I ran my provisioning engine against both template structures.</p>
<p>The result with the <strong>monolithic</strong> template: <code>PnPClientContext</code> did its job — it absorbed a lot of 429s silently and kept going. But under sustained throttling, the retries ran out, PnP threw a <code>MaximumRetryAttemptedException</code>, and my outer retry had to restart it from scratch. From list number one. Even though we were deep into page provisioning when it failed.</p>
<p>The result with <strong>modular</strong> templates: the same CSOM-level retry absorbed the same throttling. But when a sustained burst finally exhausted PnP&rsquo;s retries on the pages template, my retry loop skipped straight to <code>03-pages.xml</code> — lists and content types had already been marked done in memory and were left alone.</p>
<blockquote>
<p>This is exactly the kind of thing that looks fine in a happy-path test, but silently destroys your provisioning SLA in production.</p>
</blockquote>
<h2 id="pnpclientcontext-bump-the-retry-count-for-provisioning">PnPClientContext: Bump the Retry Count for Provisioning</h2>
<p>The PnP Framework&rsquo;s internal CSOM calls already use <code>ExecuteQueryRetry()</code> — an extension method that handles 429 throttling with exponential backoff, respecting the <code>Retry-After</code> header SharePoint returns. You get this for free with a plain <code>ClientContext</code>. The default is 10 retries.</p>
<p>If you expect heavier throttling during provisioning — and a large site with lots of lists, content types, and pages is a reasonable place to expect it — you can easily raise that limit by converting to a <code>PnPClientContext</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#66d9ef">using</span> var siteCtx = CSOMClientFactory.Create(provCtx.NewSiteUrl, credential);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> pnpCtx = PnPClientContext.ConvertFrom(siteCtx, retryCount: <span style="color:#ae81ff">15</span>);
</span></span></code></pre></div><p>That is the entire change. The provisioning code picks up the higher retry count automatically. Your outer retry logic only comes into play if throttling is so sustained that even 15 attempts on a single CSOM call are not enough — at which point PnP throws a <code>MaximumRetryAttemptedException</code>.</p>
<h2 id="idempotency-strategy">Idempotency Strategy</h2>
<p>The key property that makes modular templates safe to retry is idempotency — applying a template that has already been applied should not cause errors or duplicate data. PnP handles most of this for you: before creating a list, content type, or page, it checks whether it already exists.</p>
<p>A very simple example of how this looks in practice:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> source <span style="color:#66d9ef">in</span> config.Templates)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> template = LoadTemplate(source);
</span></span><span style="display:flex;"><span>    pnpCtx.Web.ApplyProvisioningTemplate(template, applyInfo);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If this loop is re-invoked after a failure (for example by an Azure Function retry policy), templates that already completed will be re-applied — but because each template is idempotent, that is safe. The already-provisioned lists and pages are left untouched. The only cost is the time it takes to re-apply them.</p>
<p>That wasted time is the whole point. With a monolithic template, a retry means re-applying everything from scratch. With modular templates, it means re-applying a small number of already-done features before reaching the one that actually failed.</p>
<h2 id="comparison">Comparison</h2>
<table>
  <thead>
      <tr>
          <th></th>
          <th>One Big Template</th>
          <th>Many Small Templates</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Retry granularity</strong></td>
          <td>Entire template restarts</td>
          <td>Only failed feature retries</td>
      </tr>
      <tr>
          <td><strong>Restart cost</strong></td>
          <td>High — full re-apply</td>
          <td>Low — one feature re-applies</td>
      </tr>
      <tr>
          <td><strong>Complexity</strong></td>
          <td>Low — one file, one call</td>
          <td>Medium — ordering, orchestration</td>
      </tr>
      <tr>
          <td><strong>Idempotency needed</strong></td>
          <td>Once per template</td>
          <td>Once per template (still needed)</td>
      </tr>
      <tr>
          <td><strong>DevProxy testability</strong></td>
          <td>Easy to test the whole flow</td>
          <td>Easier to isolate a specific feature</td>
      </tr>
      <tr>
          <td><strong>Best for</strong></td>
          <td>Simple sites, low throttle risk</td>
          <td>Complex sites, production resilience</td>
      </tr>
  </tbody>
</table>
<h2 id="verdict">Verdict</h2>
<p>Neither approach is universally correct. If you are provisioning simple sites with low template complexity and you are not worried about throttling, a monolithic template is perfectly fine and a lot less work to maintain.</p>
<p>But if you are building a production provisioning engine that needs to be resilient — where throttling is a real risk, where retries need to be efficient, and where you care about how long a failure takes to recover from — modular templates win clearly.</p>
<p>The combination that works best for me is: <strong>modular templates</strong> + <strong>PnPClientContext</strong> with conservative retry settings. PnPClientContext handles the low-level CSOM throttling so most 429s never surface at all. When they do surface (and eventually they will), modular templates ensure your retry loop does the minimum amount of work to get back on track.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The biggest takeaway for me is not even the monolith-vs-modular debate. It is that <strong>I never would have found this out without DevProxy</strong>. Happy-path testing showed no difference between the two approaches. It was only when I introduced realistic throttling that the behavior diverged in a way that actually matters.</p>
<p>If you are building any kind of SharePoint automation that makes CSOM or Graph calls under load, DevProxy should be a standard part of your testing setup. It is free, it integrates with your local development environment, and it reveals exactly the kind of subtle failure modes that only appear in production.</p>
<hr>
<h3 id="references">References</h3>
<ul>
<li><a href="https://github.com/pnp/pnpframework">PnP Framework on GitHub</a></li>
<li><a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview">Dev Proxy overview</a></li>
<li><a href="https://learn.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online">SharePoint throttling and retry guidance</a></li>
<li><a href="https://pnp.github.io/pnpframework/">PnPClientContext — ExecuteQueryRetry docs</a></li>
</ul>
]]></content:encoded></item><item><title>DevProxy: How to Test API Rate Limiting and Throttling in C# Development</title><link>https://jeppe-spanggaard.dk/blogs/devproxy-throttling-testing/</link><pubDate>Sun, 10 Aug 2025 00:00:00 +0000</pubDate><author>Jeppe</author><guid>https://jeppe-spanggaard.dk/blogs/devproxy-throttling-testing/</guid><description>Discover how to simulate Microsoft Graph and SharePoint throttling locally using DevProxy, and prevent production slowdowns before they happen.</description><content:encoded><![CDATA[<h2 id="the-problem-when-parallel-programming-backfires">The Problem: When Parallel Programming Backfires</h2>
<p>If you’ve ever optimized your C# API calls with parallel programming, you might know this story.</p>
<p>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.</p>
<p><strong>Random errors. Requests delayed for 45 seconds.</strong><br>
Microsoft Graph or SharePoint has slammed you with throttling and rate limits. Your blazing-fast local solution crumbles under production-like conditions.</p>
<hr>
<h2 id="how-crud-operations-can-secretly-burn-through-your-limits--and-how-to-catch-them-with-devproxy">How CRUD Operations Can Secretly Burn Through Your Limits — and How to Catch Them with DevProxy</h2>
<p>Here’s the thing:<br>
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.</p>
<p>And those “simple” operations? They’re not as cheap as you think.</p>
<p><strong>From Microsoft’s RU table</strong>:</p>
<table>
  <thead>
      <tr>
          <th>Operation Type</th>
          <th>RU Cost</th>
          <th>What That Means</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Single item query</strong></td>
          <td>1 RU</td>
          <td>Reading one specific list item</td>
      </tr>
      <tr>
          <td><strong>Multi-item query</strong></td>
          <td>2 RUs</td>
          <td>Listing children, filtering, sorting</td>
      </tr>
      <tr>
          <td><strong>Create / Update / Delete / Upload</strong></td>
          <td>2 RUs</td>
          <td>Per individual operation (bulk operations may be more efficient)</td>
      </tr>
      <tr>
          <td><strong>Permission expansion</strong> (<code>$expand=permissions</code>)</td>
          <td>5 RUs</td>
          <td>Heavy permission lookups</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>Source:</strong> <a href="https://learn.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online#resource-units">Microsoft&rsquo;s official SharePoint throttling documentation</a></p>
</blockquote>
<p>Permission expansions are just one type of &ldquo;expensive&rdquo; call. Another common scenario that can impact RU consumption is <strong>making multiple separate queries instead of using CAML joins</strong> — something I explored in <a href="https://jeppe-spanggaard.dk/blogs/joining-multiple-lists-csom-caml/">my CAML join post</a>.<br>
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.</p>
<hr>
<h3 id="why-this-sneaks-up-on-you">Why This Sneaks Up on You</h3>
<p>When coding locally, it’s easy to run a handful of queries without noticing.<br>
But in production, with real concurrency and volume, these calls pile up in seconds.</p>
<p>Imagine:</p>
<ul>
<li>10 parallel updates (2 RUs each) = <strong>20 RUs in one burst</strong></li>
<li>Add a few joins/expansions or large list queries, and you’re suddenly <em>burning through limits 5x faster</em>.</li>
</ul>
<hr>
<h3 id="how-devproxy-can-show-you-the-pain-before-production">How DevProxy Can Show You the Pain Before Production</h3>
<p>Here’s where DevProxy shines.<br>
If you configure it to simulate throttling based on these RU-heavy calls, you’ll <em>see</em> the impact locally:</p>
<ol>
<li><strong>Enable throttling plugins</strong> in your <code>devproxy.json</code> (as shown earlier).</li>
<li>Point <code>urlsToWatch</code> at your SharePoint endpoints.</li>
<li>Run your CRUD-heavy code.</li>
</ol>
<p>DevProxy will start throwing 429s (Too Many Requests) once your “fake” RU budget is exhausted — just like SharePoint would in production.</p>
<p>The beautiful part?<br>
You can crank the limits <em>down</em> during testing to make expensive patterns obvious. Even a single large query or unbatched <code>Update</code> will light up your logs.</p>
<p>Example DevProxy log when hitting a throttling simulation:</p>
<pre tabindex="0"><code>[Warning] Throttling triggered: 2 parallel Create calls exceeded RU limit (RateLimitingPlugin)
Retry after: 30 seconds
</code></pre><p><strong>Pro tip:</strong><br>
Use DevProxy as a <strong>budget meter</strong> for your API calls. Treat every 2-RU and 5-RU operation as “spending big” — and redesign those spots <em>before</em> they become a production outage.</p>
<h2 id="what-is-devproxy">What is DevProxy?</h2>
<p><a href="https://github.com/dotnet/dev-proxy">DevProxy</a> is an open-source HTTP/HTTPS proxy server that can simulate real-world network issues, including:</p>
<ul>
<li><a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/concepts/what-is-rate-limiting"><strong>Rate limiting</strong></a></li>
<li><a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/concepts/what-is-throttling"><strong>Throttling</strong></a></li>
<li><a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/how-to/simulate-slow-api-responses"><strong>Network delays</strong></a></li>
<li><a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/how-to/test-my-app-with-random-errors"><strong>Intermittent errors</strong></a></li>
</ul>
<p>It’s free, open source, and designed for developers who want to <strong>catch performance bottlenecks before they reach production</strong>.</p>
<hr>
<h2 id="installing-devproxy">Installing DevProxy</h2>
<p>Follow the official setup guide here:<br>
<a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/get-started/set-up">DevProxy Installation Documentation</a></p>
<hr>
<h2 id="basic-usage">Basic Usage</h2>
<p>Run DevProxy with the default configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>devproxy
</span></span></code></pre></div><p>It will begin intercepting all HTTP/HTTPS requests.</p>
<blockquote>
<p><strong>Tip:</strong> Start DevProxy <em>before</em> running your own code — otherwise, it won’t capture the traffic.</p>
</blockquote>
<hr>
<h2 id="simulating-microsoft-graph-throttling">Simulating Microsoft Graph Throttling</h2>
<p>Here’s how to configure DevProxy to reproduce Microsoft Graph and SharePoint throttling issues locally.</p>
<h3 id="1-create-a-devproxyjson-configuration-file">1. Create a <code>devproxy.json</code> configuration file</h3>
<p>This is the exact configuration I used when testing my problematic parallel code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/rc.schema.json&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;rate&#34;</span>: <span style="color:#ae81ff">25</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;plugins&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;RetryAfterPlugin&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;enabled&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;pluginPath&#34;</span>: <span style="color:#e6db74">&#34;~appFolder/plugins/dev-proxy-plugins.dll&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;RateLimitingPlugin&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;enabled&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;pluginPath&#34;</span>: <span style="color:#e6db74">&#34;~appFolder/plugins/dev-proxy-plugins.dll&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;configSection&#34;</span>: <span style="color:#e6db74">&#34;rateLimitingPlugin&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;GraphRandomErrorPlugin&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;enabled&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;pluginPath&#34;</span>: <span style="color:#e6db74">&#34;~appFolder/plugins/dev-proxy-plugins.dll&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;configSection&#34;</span>: <span style="color:#e6db74">&#34;graphRandomErrorPlugin&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;urlsToWatch&#34;</span>: [
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://graph.microsoft.com/v1.0/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://graph.microsoft.com/beta/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://graph.microsoft.us/v1.0/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://graph.microsoft.us/beta/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://dod-graph.microsoft.us/v1.0/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://dod-graph.microsoft.us/beta/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://microsoftgraph.chinacloudapi.cn/v1.0/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://microsoftgraph.chinacloudapi.cn/beta/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://*.sharepoint.*/*_api/web/GetClientSideComponents&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://*.sharepoint.*/*_api/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://*.sharepoint.*/*_vti_bin/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://*.sharepoint-df.*/*_api/*&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;https://*.sharepoint-df.*/*_vti_bin/*&#34;</span>
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;graphRandomErrorPlugin&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/graphrandomerrorplugin.schema.json&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;allowedErrors&#34;</span>: [
</span></span><span style="display:flex;"><span>      <span style="color:#ae81ff">429</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ae81ff">503</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ae81ff">504</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;rateLimitingPlugin&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.27.0/ratelimitingplugin.schema.json&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;costPerRequest&#34;</span>: <span style="color:#ae81ff">2</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;rateLimit&#34;</span>: <span style="color:#ae81ff">120</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;logLevel&#34;</span>: <span style="color:#e6db74">&#34;information&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;newVersionNotification&#34;</span>: <span style="color:#e6db74">&#34;stable&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;showSkipMessages&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;showTimestamps&#34;</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="2-start-devproxy-with-your-configuration">2. Start DevProxy with your configuration</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>devproxy --config-file devproxy.json
</span></span></code></pre></div><hr>
<h2 id="example-of-problematic-code">Example of Problematic Code</h2>
<p>Here’s the snippet that caused my throttling nightmare. It uses <code>Parallel.ForEachAsync</code> to query SharePoint in bulk:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#75715e">// ❌ This will likely trigger throttling in production</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> customers = <span style="color:#66d9ef">new</span> ConcurrentBag&lt;CustomerDTO&gt;();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">await</span> Parallel.ForEachAsync(departmentIds, <span style="color:#66d9ef">async</span> (departmentId, token) =&gt; 
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> clonedContext = _clientContext.Clone(_clientContext.Url);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> query = Camlex.Query()
</span></span><span style="display:flex;"><span>        .ViewFields(<span style="color:#66d9ef">new</span> CustomerDTO().ViewFields().ToArray().Append(<span style="color:#e6db74">&#34;DepartmentId&#34;</span>))
</span></span><span style="display:flex;"><span>        .LeftJoin(x =&gt; x[<span style="color:#e6db74">&#34;DepartmentLookup&#34;</span>].ForeignList(DEPARTMENT_LIST_GUID))
</span></span><span style="display:flex;"><span>        .ProjectedField(x =&gt; x[<span style="color:#e6db74">&#34;DepartmentId&#34;</span>].List(DEPARTMENT_LIST_GUID).ShowField(<span style="color:#e6db74">&#34;ID&#34;</span>))
</span></span><span style="display:flex;"><span>        .Where(x =&gt; x[<span style="color:#e6db74">&#34;DepartmentId&#34;</span>] == departmentId);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> departmentCustomers = <span style="color:#66d9ef">await</span> SharePointService.GetItemsFromListByQuery&lt;CustomerDTO&gt;(
</span></span><span style="display:flex;"><span>        CUSTOMER_LIST_GUID,
</span></span><span style="display:flex;"><span>        clonedContext,
</span></span><span style="display:flex;"><span>        query);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (departmentCustomers?.Any() == <span style="color:#66d9ef">true</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> customer <span style="color:#66d9ef">in</span> departmentCustomers)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            customers.Add(customer);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><hr>
<h2 id="why-this-code-fails-in-production">Why This Code Fails in Production</h2>
<ol>
<li><strong>No throttling safeguards</strong> — Multiple parallel requests overwhelm SharePoint.</li>
<li><strong>No retry logic</strong> — Requests fail instead of recovering.</li>
<li><strong>No request limiting</strong> — All departments are processed simultaneously.</li>
<li><strong>Silent failures</strong> — Errors are ignored without logging or fallback.</li>
</ol>
<hr>
<h2 id="the-better-way">The Better Way</h2>
<p>Instead of hard-coding fixes here, I recommend Bert Jansen’s detailed guide on throttling and rate limit handling:<br>
<a href="https://github.com/OneDrive/samples/blob/master/scenarios/throttling-ratelimit-handling/readme.md">Throttling &amp; Rate Limit Handling Patterns</a></p>
<p>Key principles:</p>
<ul>
<li><strong>Limit concurrency</strong> with <code>SemaphoreSlim</code>.</li>
<li><strong>Use exponential backoff</strong> for retries.</li>
<li><strong>Monitor rate limit headers</strong> to adjust requests dynamically.</li>
<li><strong>Handle errors explicitly</strong> to prevent silent failures.</li>
</ul>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>DevProxy has become an essential tool in my Microsoft 365 development workflow. It helps me:</p>
<ul>
<li>Catch throttling issues <strong>before</strong> production.</li>
<li>Test error handling and retry logic <strong>locally</strong>.</li>
<li>Deliver applications that can survive real-world API limits.</li>
</ul>
<p>If I’d used DevProxy from the start, I could have avoided the last-minute throttling meltdown entirely.</p>
<p>💡 <strong>Pro tip:</strong> Make DevProxy part of your <em>early</em> development process, not your emergency toolkit.</p>
<hr>
<h2 id="additional-resources">Additional Resources</h2>
<ul>
<li><a href="https://github.com/dotnet/dev-proxy">DevProxy GitHub Repository</a></li>
<li><a href="https://docs.microsoft.com/en-us/graph/throttling">Microsoft Graph Throttling Guidelines</a></li>
<li><a href="https://aka.ms/devproxy/docs">DevProxy Documentation</a></li>
</ul>
]]></content:encoded></item></channel></rss>