<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ilya</title>
    <description>The latest articles on Forem by Ilya (@ilya-chumakov).</description>
    <link>https://forem.com/ilya-chumakov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1671561%2F7e9999a3-f98a-45ba-a8d6-49348af5b128.jpg</url>
      <title>Forem: Ilya</title>
      <link>https://forem.com/ilya-chumakov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ilya-chumakov"/>
    <language>en</language>
    <item>
      <title>What is inside Rate Limiting for .NET</title>
      <dc:creator>Ilya</dc:creator>
      <pubDate>Tue, 19 Nov 2024 15:05:00 +0000</pubDate>
      <link>https://forem.com/ilya-chumakov/what-is-inside-rate-limiting-for-net-58n7</link>
      <guid>https://forem.com/ilya-chumakov/what-is-inside-rate-limiting-for-net-58n7</guid>
      <description>&lt;p&gt;The Rate Limiting API debuted in .NET 7. It implements several popular algorithms for limiting the number of requests to a shared resource. This API is typically &lt;a href="https://servicestack.net/posts/asp-rate-limiter-middleware" rel="noopener noreferrer"&gt;promoted&lt;/a&gt; as a part of a built-in rate-limiting ASP.NET Core middleware. However, the API itself doesn't depend on ASP.NET and has a broader scope. This API is &lt;em&gt;recently&lt;/em&gt; written and may reflect the current state of concurrency in .NET. Finally, it's a production-ready library, not a book example of a semaphore in a loop with sleep. So let's take a look inside and see if we can learn something.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate Limiting API overview
&lt;/h3&gt;

&lt;p&gt;Disclaimer: In a mature system, and for the sake of horizontal scaling, rate limiting is often implemented at the infrastructure layer (e.g. &lt;a href="https://blog.nginx.org/blog/rate-limiting-nginx" rel="noopener noreferrer"&gt;Rate Limiting with NGINX&lt;/a&gt;), except for some tricky quota partitioning driven by domain logic.&lt;/p&gt;

&lt;h4&gt;
  
  
  Middleware
&lt;/h4&gt;

&lt;p&gt;As mentioned above, there is a built-in &lt;a href="https://github.com/dotnet/aspnetcore/blob/release/8.0/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs" rel="noopener noreferrer"&gt;RateLimitingMiddleware&lt;/a&gt; in ASP.NET Core. Its basic usage is extensively covered in Microsoft Learn and community blogs, so allow me to skip it. There is not much inside: the midlleware basically does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing a limiter by the endpoint's metatada and global settings.&lt;/li&gt;
&lt;li&gt;Trying to acquire the protected resource (i.e. the endpoint).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things get more interesting at the rate limiter level. &lt;/p&gt;

&lt;h4&gt;
  
  
  Limiting algorithms
&lt;/h4&gt;

&lt;p&gt;System.Threading.RateLimiting namespace contains limiters that implement the following algorithms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-8.0#fixed" rel="noopener noreferrer"&gt;Fixed window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-8.0#slide" rel="noopener noreferrer"&gt;Sliding window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-8.0#token-bucket-limiter" rel="noopener noreferrer"&gt;Token bucket&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-8.0#concurrency-limiter" rel="noopener noreferrer"&gt;Concurrency&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each limiter, except ConcurrencyLimiter, caps the number of requests in a time period. ConcurrencyLimiter caps only the number of concurrent requests. All limiters are derived from the abstract &lt;a href="https://github.com/dotnet/runtime/blob/release/8.0/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/RateLimiter.cs" rel="noopener noreferrer"&gt;RateLimiter&lt;/a&gt; class and implement the following public API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fast synchronous attempt to acquire permits.&lt;/span&gt;
&lt;span class="n"&gt;RateLimitLease&lt;/span&gt; &lt;span class="nf"&gt;AttemptAcquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;permitCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Await until the requested permits are available&lt;/span&gt;
&lt;span class="c1"&gt;// or can no longer be acquired.&lt;/span&gt;
&lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RateLimitLease&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AcquireAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;permitCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see how both methods &lt;a href="https://github.com/dotnet/aspnetcore/blob/release/8.0/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs#L153" rel="noopener noreferrer"&gt;are used&lt;/a&gt; at the middleware level. First, a fast sync attempt is made. If it fails, then the async method is called, possibly hitting a wait queue (I discuss this queue in the next section).&lt;/p&gt;

&lt;h4&gt;
  
  
  Partitioning and chaining
&lt;/h4&gt;

&lt;p&gt;Another API worth mentioning is &lt;code&gt;PartitionedRateLimiter&amp;lt;TResource&amp;gt;&lt;/code&gt;, which is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Partitioning: selecting a limiter at runtime by the TResource value.&lt;/li&gt;
&lt;li&gt;Chaining: combining limiters into a sequence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example of partitioning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;limiter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PartitionedRateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"foo_resource_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RateLimitPartition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFixedWindowLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;FixedWindowRateLimiterOptions&lt;/span&gt; &lt;span class="p"&gt;{...});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RateLimitPartition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConcurrencyLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConcurrencyLimiterOptions&lt;/span&gt; &lt;span class="p"&gt;{...});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lease&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AttemptAcquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo_resource_id"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An example of chaining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;limiter1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PartitionedRateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RateLimitPartition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;concurrencyLimiter1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;limiter2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PartitionedRateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RateLimitPartition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;concurrencyLimiter2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chainedLimiter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PartitionedRateLimiter&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateChained&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;limiter1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limiter2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that chaining is only supported for partitioned limiters. Non-partitioned limiters must be wrapped into partitioned ones. This wrapping doesn't prevent or otherwise affect the direct use of original limiters, i.e. the same instance can be used as a part of a chain and independently at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is inside a limiter?
&lt;/h3&gt;

&lt;p&gt;Let's discuss the main components of a limiter. It may seem obvious, but to implement a public API as in Limiting algorithms we need a permit counter, and a wait queue. For time-based limiters, we also need a timer.&lt;/p&gt;

&lt;h4&gt;
  
  
  Counter
&lt;/h4&gt;

&lt;p&gt;Each limiter must track the amount of available permits. There is no fancy data structure behind this: &lt;code&gt;_permitCount&lt;/code&gt; counter is just an integer private field. The real challenge is to correctly update that little cute field in a multi-threaded environment. For example, the inner logic of the AttemptAcquire method of ConcurrencyLimiter looks pretty simple...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;RateLimitLease&lt;/span&gt; &lt;span class="nf"&gt;AttemptAcquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;permitCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_permitCount&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;permitCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// it tries to reduce _permitCount inside:&lt;/span&gt;
            &lt;span class="c1"&gt;// _permitCount -= permitCount;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;TryLeaseUnsynchronized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permitCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;RateLimitLease&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;lease&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ignoring the fact that I've omitted 99% of the supplementary code, which covers thread synchronisation and various edge cases. A LOT of edge cases. Ensuring secure write access to shared data is inherently complex, and the limiter's complete code is no joke. And there is even more to discuss.&lt;/p&gt;

&lt;h4&gt;
  
  
  Wait queue
&lt;/h4&gt;

&lt;p&gt;Any limiter from above supports &lt;em&gt;queuing&lt;/em&gt; permit requests, which can be de-queued (FIFO or LIFO) at some point in the future when the permits become available. Suitable job for a double-linked list, or a double-ended queue! The developers chose the latter, and built the wait queue on top of the &lt;em&gt;internal&lt;/em&gt; &lt;a href="https://github.com/dotnet/runtime/blob/release/8.0/src/libraries/Common/src/System/Collections/Generic/Deque.cs" rel="noopener noreferrer"&gt;Deque&lt;/a&gt; collection. There is a long-term &lt;a href="https://github.com/dotnet/runtime/issues/935" rel="noopener noreferrer"&gt;discussion&lt;/a&gt; about implementing a public version of Deque, but unfortunately it is not even close to a deal.&lt;/p&gt;

&lt;p&gt;This double-ended queue has many similarities with such well-known collections as &lt;code&gt;Stack&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code&gt;Queue&amp;lt;T&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is built on top of... a simple &lt;code&gt;T[]&lt;/code&gt; array with some index math around it.&lt;/li&gt;
&lt;li&gt;It grows in size by allocating a new array of x2 size when it reaches the current array capacity.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;not&lt;/strong&gt; thread-safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point is the most important: again, &lt;code&gt;lock&lt;/code&gt; statements are heavily used to ensure exclusive access to a shared Deque instance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Timer
&lt;/h4&gt;

&lt;p&gt;Another important building block is a &lt;em&gt;timer&lt;/em&gt;. Indeed, except for &lt;code&gt;ConcurrencyLimiter&lt;/code&gt;, each of the rate limiting algorithms considered has to repeat an action over time (to replenish the bucket, move the window and so on). This is where &lt;code&gt;System.Threading.Timer&lt;/code&gt; comes in handy, it's a lightweight class that can execute a callback method on a thread from the thread pool at regular intervals. &lt;/p&gt;

&lt;p&gt;For &lt;code&gt;TokenBucketRateLimiter&lt;/code&gt;, implementing bucket replenishment is pretty straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;_renewTimer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;Replenish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplenishmentPeriod&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things get more interesting in &lt;code&gt;DefaultPartitionedRateLimiter&amp;lt;TResource, TKey&amp;gt;&lt;/code&gt;, where &lt;code&gt;TimerAwaitable&lt;/code&gt; is introduced as an internal async-over-sync wrapper over the synchronous &lt;code&gt;System.Threading.Timer&lt;/code&gt; API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;_timer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TimerAwaitable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timerInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timerInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;_timerTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;RunTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RunTimer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// handle a cache of created limiters&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Heartbeat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TimerAwaitable&lt;/code&gt; is a good reminder of the fact that not every awaitable expression has to be a Task. In fact, &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12982-awaitable-expressions" rel="noopener noreferrer"&gt;the compiler requirements for 'await'&lt;/a&gt; are quite loose. &lt;/p&gt;

&lt;p&gt;Let's have a look at what's in &lt;code&gt;TimerAwaitable&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Tick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exchange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_callbackCompleted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TimerAwaitable&lt;/span&gt; &lt;span class="nf"&gt;GetAwaiter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsCompleted&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_callbackCompleted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;GetResult&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_callback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_running&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a &lt;code&gt;System.Threading.Timer&lt;/code&gt; instance with only one job: on every "tick" set &lt;code&gt;_callback&lt;/code&gt; field to its "completed" value. This field, despite the name, is mostly used to determine whether this quazi-task is completed or not. The value is later reset to null in the &lt;code&gt;GetResult&lt;/code&gt; call. These methods plus the implementation of the &lt;a href="https://stackoverflow.com/questions/65529509/what-is-icriticalnotifycompletion-for" rel="noopener noreferrer"&gt;ICriticalNotifyCompletion&lt;/a&gt; interface allow to await the timer instance itself, without allocation of a new task!&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage Example
&lt;/h3&gt;

&lt;p&gt;You might have expected an example of how to use the Rate Limiting API at the end of this post. I was going to, but then I had a better idea. In this post I've mostly shown code written by someone else (probably by a better software engineer than me), so let's continue that exploitation.&lt;/p&gt;

&lt;p&gt;This API is extensively covered by tests, &lt;a href="https://github.com/dotnet/runtime/blob/release/8.0/src/libraries/System.Threading.RateLimiting/tests/PartitionedRateLimiterTests.cs#L126" rel="noopener noreferrer"&gt;like this one&lt;/a&gt;. These tests are a better example than I could ever provide. They are isolated and run locally, assuming you have cloned and built the dotnet/runtime repo on your machine. I strongly recommend that you give it a try. Building the entire .NET runtime from source may seem intimidating at first, and it will take some time. But if you follow the &lt;a href="https://github.com/dotnet/runtime/blob/release/8.0/docs/workflow/README.md" rel="noopener noreferrer"&gt;Workflow Guide&lt;/a&gt;, this mega project shall compile without a single warning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this post I discussed the internals of the built-in .NET Rate Limiting API. I showed how the limiter behavour can be tailored to your application's needs by using techniques such as partitioning and chaining. I examined the source code in the System.Threading.RateLimiting namespace and discussed the key components of a limiter: a counter, a wait queue, and a timer. &lt;/p&gt;

&lt;p&gt;The most popular examples of using the Rate Limiting API are also the most controversial. It's natural to enable rate limiting for inbound HTTP requests at the infrastructure layer, while outbound limiting is easily implemented within an application using &lt;a href="https://www.pollydocs.org/" rel="noopener noreferrer"&gt;Polly&lt;/a&gt;. I would love to hear about other real-world uses of the Rate Limiting API, so please let me know if you have one.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aspnet</category>
      <category>csharp</category>
      <category>dotnetcore</category>
    </item>
    <item>
      <title>Introducing Awesome .NET Testing ✅</title>
      <dc:creator>Ilya</dc:creator>
      <pubDate>Mon, 14 Oct 2024 13:30:00 +0000</pubDate>
      <link>https://forem.com/ilya-chumakov/introducing-awesome-net-testing-4m2a</link>
      <guid>https://forem.com/ilya-chumakov/introducing-awesome-net-testing-4m2a</guid>
      <description>&lt;p&gt;I have a confession to make. I love writing automated tests. That's why I've created &lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing" rel="noopener noreferrer"&gt;awesome-dotnet-testing&lt;/a&gt; – a curated list of awesome .NET libraries and tools that help ensure code quality through automated testing. While my source of inspiration was &lt;a href="https://github.com/thangchung/awesome-dotnet-core" rel="noopener noreferrer"&gt;awesome-dotnet-core&lt;/a&gt;, sadly that massive project doesn't seem to be actively maintained anymore, with some notable projects missing, and some obsolete. This is why I have started a new &lt;strong&gt;categorised&lt;/strong&gt; list with a focus solely on testing, and I'm going to keep it up to date and comprehensive. I've even learned a few new things along the way, even though I've been working with testing on .NET for a long time. So I would be happy if it helps someone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contents so far
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#intro-test-frameworks" rel="noopener noreferrer"&gt;Intro. Test frameworks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#assertions" rel="noopener noreferrer"&gt;Assertions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#behavior-driven-development-bdd" rel="noopener noreferrer"&gt;Behavior Driven Development (BDD)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#code-coverage" rel="noopener noreferrer"&gt;Code coverage&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#coverage-collection" rel="noopener noreferrer"&gt;Coverage collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#coverage-visualization" rel="noopener noreferrer"&gt;Coverage visualization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#fake-data-generators" rel="noopener noreferrer"&gt;Fake data generators&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#mocks" rel="noopener noreferrer"&gt;Mocks&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#general-purpose-mocks" rel="noopener noreferrer"&gt;General purpose mocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#specific-mocks" rel="noopener noreferrer"&gt;Specific mocks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#mutations" rel="noopener noreferrer"&gt;Mutations&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#random--fuzzy-testing" rel="noopener noreferrer"&gt;Random &amp;amp; fuzzy testing&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#security" rel="noopener noreferrer"&gt;Security&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#snapshots" rel="noopener noreferrer"&gt;Snapshots&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#web-ui-test-automation" rel="noopener noreferrer"&gt;Web UI test automation&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#uncategorized" rel="noopener noreferrer"&gt;Uncategorized&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/ilya-chumakov/awesome-dotnet-testing/#appendix-i-xunit-extensions" rel="noopener noreferrer"&gt;Appendix I. xUnit extensions&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>testing</category>
      <category>dotnetcore</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Designing HTTP API clients in .NET</title>
      <dc:creator>Ilya</dc:creator>
      <pubDate>Wed, 21 Aug 2024 08:00:00 +0000</pubDate>
      <link>https://forem.com/ilya-chumakov/designing-http-api-clients-in-net-j9e</link>
      <guid>https://forem.com/ilya-chumakov/designing-http-api-clients-in-net-j9e</guid>
      <description>&lt;p&gt;In this post, I describe a scalable architectural approach to building typed HTTP API clients for ASP.NET Core applications. I discuss how following this approach can contribute to API testability and maintainability. I provide some practical examples and highlight common pitfalls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem definition
&lt;/h3&gt;

&lt;p&gt;Distributed systems are popular. HTTP APIs are &lt;em&gt;very&lt;/em&gt; popular. And .NET is a reasonable tool for implementing both, starting from the classic RESTful APIs and microservices-oriented architecture.&lt;/p&gt;

&lt;p&gt;While one of the arguments in favour of microservices is sharing the workload between multiple development teams using different programming languages (and who said anything about &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_law" rel="noopener noreferrer"&gt;Conway's Law&lt;/a&gt;), in this article I'd like to talk about a homogeneous, pure .NET ecosystem. Most of the stuff below is relevant to ASP.NET Core Web API applications. Over the years, I have designed, implemented, or overseen many of these, and I want to share the best practices I have learned. &lt;/p&gt;

&lt;p&gt;Let's start with a published REST API. On the consumer side, managed by another team, the typed HTTP client code is written. Hopefully, its implementation is based on some docs shipped with the API. Then it just works, right? Well, yes and no. A typical enterprise-grade distributed system may have &lt;em&gt;thousands&lt;/em&gt; of endpoints and hundreds of respectful consumers, developed and maintained by dozens of teams. Independently re-creating HTTP clients from scratch in each and every consumer project is out of the question, because it doesn't scale at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foep2dzvx29gat98ygmql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foep2dzvx29gat98ygmql.png" alt="API hell" width="660" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are several ways to save time and reduce the amount of repeated boilerplate code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reuse existing client code (NuGet).&lt;/li&gt;
&lt;li&gt;Use a high-level client builder for each client.&lt;/li&gt;
&lt;li&gt;Generate client code on each consumer using some form of specification from the producer (OpenAPI).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And while this post revolves mostly around NuGet, it would be wrong of me not to mention the others.&lt;/p&gt;

&lt;h4&gt;
  
  
  High-level client builders
&lt;/h4&gt;

&lt;p&gt;There are many third-party REST client builders for .NET on GitHub, and new ones keep popping up for some reason. Some of the alive and popular are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tmenier/Flurl" rel="noopener noreferrer"&gt;Flurl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/canton7/RestEase" rel="noopener noreferrer"&gt;RestEase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/restsharp/RestSharp" rel="noopener noreferrer"&gt;RestSharp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/reactiveui/refit" rel="noopener noreferrer"&gt;Refit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part of the community is quite happy using these libraries. However, based on my experience so far, and considering the occasional problems I've encountered (limitations, missing features, or questionable design), I wouldn't make a high-level builder the default choice in a large system. But never say never.&lt;/p&gt;

&lt;h4&gt;
  
  
  OpenAPI / Swagger
&lt;/h4&gt;

&lt;p&gt;Swagger is a widely used toolset based on the &lt;a href="https://www.openapis.org/" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt; specification that needs no introduction. For instance, an OpenAPI/Swagger schema can be generated from ASP.NET Core app using &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger" rel="noopener noreferrer"&gt;Swashbuckle&lt;/a&gt;, NSwag, or the new fancy &lt;a href="https://devblogs.microsoft.com/dotnet/dotnet9-openapi/" rel="noopener noreferrer"&gt;.NET 9 built-in generator&lt;/a&gt;. And then, given that document, the corresponding client can be generated in any popular programming language. &lt;a href="https://github.com/christianhelle/apiclientcodegen" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is a detailed list of .NET libraries for client code generation.&lt;/p&gt;

&lt;p&gt;However, OpenAPI's declarative resource specification does not cover your implementation details. It's simply designed to do the opposite: provide a language-agnostic way to express an API contract. And what are implementation details of an HTTP client? Authentication, authorization, serialization, validation, error handling, and so on.&lt;/p&gt;

&lt;p&gt;Don't get me wrong: Swagger is great, use it, and keep the documents up to date. Your colleagues will appreciate it. But for the .NET parts of a distributed system we can smooth backend-to-backend integrations... with good old NuGet packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution structure
&lt;/h3&gt;

&lt;p&gt;Here is the proposed dependency diagram for a typical ASP.NET Core application that implements &lt;code&gt;POST /orders&lt;/code&gt; REST API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pul9hpmzchxgo8nlxoa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pul9hpmzchxgo8nlxoa.png" alt="Solution structure" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As the root, there is Orders.Client class library containing a typed client and, naturally, all its input/output DTOs.  This library is also published as NuGet package.&lt;/li&gt;
&lt;li&gt;Web project depends directly on Orders.Client, and reuses ApiModels as endpoints' inputs/outputs.&lt;/li&gt;
&lt;li&gt;Tests project reuses the typed client in API tests (I explain my definition of "API tests" in the next section).&lt;/li&gt;
&lt;li&gt;Finally, an external consumer of &lt;code&gt;POST /orders&lt;/code&gt; references the Orders.Client NuGet package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example of an input/output DTO could be &lt;code&gt;OrderCreateApiModel&lt;/code&gt;, into which the &lt;code&gt;POST /orders&lt;/code&gt; request body is deserialized. By the way, I prefer &lt;em&gt;public contracts&lt;/em&gt; like this one to follow some global naming convention, i.e. "ApiModel" in this case. Such a rule serves well as a big red sign that any thoughtless change of such type can break the API backward comparability. &lt;/p&gt;

&lt;p&gt;Typical code for each project might look like this:&lt;/p&gt;

&lt;p&gt;Orders.Client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//omitted for brewity: interfaces, cancellation tokens, attributes etc. &lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOrdersClient&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderApiModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderApiModel&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Orders.Web:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;OrderController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ApiController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderApiModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderApiModel&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Orders.Web.Tests (based on &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0#basic-tests-with-the-default-webapplicationfactory" rel="noopener noreferrer"&gt;WebApplicationFactory&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VeryBasicTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IClassFixture&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;OrderClient&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;see cref="OrderController.Create" /&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderAsync_Default_OK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderApiModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{...});&lt;/span&gt;
        &lt;span class="c1"&gt;//omitted for brewity: Act, Assert steps&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advantages of sharing a typed client project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Explicit contract. The Orders.Client project declares and maintains a &lt;em&gt;public contract&lt;/em&gt;. All API changes start from here. During code review, this project is your starting point for hunting API-breaking changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reusable code. As you can see in the above code, the same &lt;code&gt;ApiModel&lt;/code&gt; types are used in a typed client, a controller, a NuGet package, even in API tests!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tested code. You write API tests against the typed client, which means you also test the public client itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code scaffold assistance. Having only the client interface, it is possible to generate a stub for the client itself, for the referenced ASP.NET Core controller/action (and vice versa). Generate a fully functional API test for each method of the typed client (i.e., for each public endpoint you declare). &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy control over the &lt;em&gt;dependencies&lt;/em&gt; of your contracts. You can even automate it by writing architectural tests (&lt;a href="https://github.com/BenMorris/NetArchTest" rel="noopener noreferrer"&gt;NetArchTest&lt;/a&gt;, &lt;a href="https://github.com/TNG/ArchUnitNET" rel="noopener noreferrer"&gt;ArchUnitNET&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frankly, I want to have the same project structure even when there are no NuGet package consumers planned, see below.&lt;/p&gt;

&lt;h3&gt;
  
  
  API tests
&lt;/h3&gt;

&lt;p&gt;Disclaimer: An effective approach to testing is to balance efforts across different &lt;a href="https://microsoft.github.io/code-with-engineering-playbook/automated-testing/e2e-testing/testing-comparison/" rel="noopener noreferrer"&gt;levels of testing&lt;/a&gt;, i.e. to follow &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html" rel="noopener noreferrer"&gt;The Test Pyramid&lt;/a&gt; paradigm.  And while this section focuses on in-memory unit/integration testing, nothing I say below implies in any way that system/end-to-end tests are not needed (they are the absolute must).&lt;/p&gt;

&lt;h4&gt;
  
  
  Advantages of using a typed client in testing
&lt;/h4&gt;

&lt;p&gt;In-memory HTTP API testing can be tricky. Fortunately, ASP.NET Core ships with an &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests#aspnet-core-integration-tests" rel="noopener noreferrer"&gt;in-memory test server&lt;/a&gt; that effectively reduces the cost of client-server emulation, and has full support for typed clients. Let's examine the benefits of reusing a typed client in tests:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unified codebase.&lt;/strong&gt; If you don't specify a team-wide convention for declaring a &lt;em&gt;public contract&lt;/em&gt; in the form of a well-designed typed HTTP client... Then, it's an invitation to take a quick-and-dirty approach to writing API tests. Such as hardcoded calls to &lt;code&gt;HttpClient&lt;/code&gt;, or at best a homemade typed client with potentially wrong serialization settings or a messed up HTTP handler pipeline. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code coverage insights.&lt;/strong&gt; Without a facade for making API calls, uncovered endpoints can only be reliably tracked at the coverage analysis stage. This is not bad, but we can do better. If the public method of a typed client has no usages, this &lt;em&gt;usually&lt;/em&gt; means that it is not explicitly called in a test. In effect, it means that the referenced API endpoint itself is not covered. These client's unused methods are easy prey for static code analyzers, giving you compile-time test coverage insights before you even run a code coverage collector.&lt;/p&gt;

&lt;p&gt;Therefore, even if there are no .NET consumers for your APIs, building a typed client is still recommended.&lt;/p&gt;

&lt;h4&gt;
  
  
  Giving API tests the right purpose
&lt;/h4&gt;

&lt;p&gt;Then let's talk about &lt;em&gt;what logic&lt;/em&gt; API tests should cover. Assuming you have separate sets of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;End-to-end language-agnostic tests (this is where Swagger can help a lot), ideally run somewhere in a test environment.&lt;/li&gt;
&lt;li&gt;Unit and integration tests that cover business logic and data access layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then there is little reason to play in their domain. On the contrary, at the API testing level, I'd like to have tested only the implementation details of the HTTP APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint address.&lt;/li&gt;
&lt;li&gt;(De)serialization of input/output data.&lt;/li&gt;
&lt;li&gt;Application-level error handling (usually implemented via ASP.NET exception handler or middleware).&lt;/li&gt;
&lt;li&gt;Middleware pipeline.&lt;/li&gt;
&lt;li&gt;Authorization policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus the less business logic involved in API tests, the better. Mock it. Make the tests simple, fast, isolated, and suitable for &lt;a href="https://www.jetbrains.com/help/dotcover/Continuous_Testing.html" rel="noopener noreferrer"&gt;continuous testing&lt;/a&gt;. This approach fits well with thin controllers (using CQRS and/or the Mediator pattern) if your controller's actions have a single, easy to mock dependency a'la &lt;code&gt;IRequestHandler&amp;lt;TInput, TOutput&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderApiModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromServices&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
    &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderApiModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderApiModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
    &lt;span class="n"&gt;CreateOrderApiModel&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business logic is effectively decoupled from the communication layer.&lt;/li&gt;
&lt;li&gt;API tests cover only the communication layer.&lt;/li&gt;
&lt;li&gt;No messing with &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/testing?view=aspnetcore-8.0" rel="noopener noreferrer"&gt;controllers in tests&lt;/a&gt;. Testing a fat controller may be tricky because of one big implicit dependency: HttpContext. In short, I recommend abstaining from manual controller creation in tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The other decorating handler
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-8.0#outgoing-request-middleware" rel="noopener noreferrer"&gt;Custom HTTP handlers&lt;/a&gt; are well known as a mechanism to manage cross-cutting concerns around HTTP requests. The calling application has control over the HTTP handler pipeline, so it can be reconfigured, reordered, or even rebuilt from scratch. Decorating a client with a &lt;a href="https://docs.duendesoftware.com/identityserver/v7/bff/tokens/" rel="noopener noreferrer"&gt;Token Management Handler&lt;/a&gt; or a custom &lt;a href="https://github.com/App-vNext/Polly" rel="noopener noreferrer"&gt;Polly&lt;/a&gt; policy is easy... assuming the client accepts an &lt;code&gt;HttpClient&lt;/code&gt; parameter in its constructor, and you haven't messed with the natural order of things by obstructing the client customization in some way (I really don't want to show how).&lt;/p&gt;

&lt;p&gt;One thing to emphasize here is that a consumer may have their own reasons for decorating HTTP calls. So even if you are driven by good intentions, keep the public constructor. Even if your API needs that bunch of client-side decorators so badly that you bundled it with the client, at least provide an extension point for the handler pipeline. Otherwise, one day you will find that your "smart" factory method has just been decompiled and rewritten on the consumer side. Just because it was cheaper and faster than asking your team for changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  DI registration
&lt;/h3&gt;

&lt;p&gt;As mentioned above, keep the registration simple and open for extension. For instance, such an unobtrusive registration could be a wrap over a built-in &lt;code&gt;AddHttpClient&amp;lt;TInterface, TImpl&amp;gt;&lt;/code&gt; call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IHttpClientBuilder&lt;/span&gt; &lt;span class="n"&gt;AddClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TImpl&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IServiceProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getBaseAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// important&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TImpl&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TInterface&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TInterface&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//any registration method should be self-contained if possible&lt;/span&gt;
    &lt;span class="nc"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddYourCustomHttpHandlers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{...});&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TImpl&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getBaseAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetYourCustomHttpHandlers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;//builder is returned to allow further extension&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caller side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryAddSimpleClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFooClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FooClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see from the code above, instead of passing a settings object directly to the &lt;code&gt;AddClient&lt;/code&gt; method, a delegate is passed. This doesn't force the caller to provide the dependency &lt;em&gt;immediately&lt;/em&gt; and works well with ASP.NET Core configuration.&lt;/p&gt;

&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make the registration method self-contained.&lt;/li&gt;
&lt;li&gt;Explicitly declare dependencies.&lt;/li&gt;
&lt;li&gt;Never ask for configuration values before the ServiceProvider is built.&lt;/li&gt;
&lt;li&gt;Let the caller decide how to resolve dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adding client-side logic
&lt;/h3&gt;

&lt;p&gt;How about something tightly coupled to the typed client itself? Like input validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderApiModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderApiModel&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfInvalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;ThrowIfInvalid(input)&lt;/code&gt; promises great benefits indeed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No HTTP call on invalid input.&lt;/li&gt;
&lt;li&gt;Full error data is provided to the caller: we avoid the pain of stripping an exception of all security-sensitive info, fitting it into a ProblemDetails compliant response (a'la &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-8.0#validation-failure-error-response" rel="noopener noreferrer"&gt;ValidationProblemDetails&lt;/a&gt;), and deserializing it on the client side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What could go wrong? Well, depending on the validator, we may have just duplicated our business logic to an unknown number of external systems, effectively reducing the effort of building a distributed application almost to zero. While &lt;em&gt;validating&lt;/em&gt; the input data makes some sense, &lt;em&gt;business rules&lt;/em&gt; (I have covered the difference in more detail &lt;a href="https://ilya-chumakov.com/nested-validation-in-.net/#what-is-validation" rel="noopener noreferrer"&gt;here&lt;/a&gt;) should under no circumstances leak to the caller.&lt;/p&gt;

&lt;p&gt;With this in mind, I would recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeping business logic where it belongs: in the domain. &lt;/li&gt;
&lt;li&gt;Reducing a typed client's inner logic to the bare minumum.&lt;/li&gt;
&lt;li&gt;Treating any typed client's auxiliary inner logic as a "high risk – low reward" move.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Making the client dependent on another package
&lt;/h3&gt;

&lt;p&gt;Another questionable design decision is adding external dependencies to a typed client assembly. For example, referencing a core package with helper methods, default serialization settings (which I highly recommend defining), and all that shared sfuff. Another example might be using a third-party library. Like Newtonsoft.Json was the preferred way to handle JSON input/output in a typed client before we got System.Text.Json. And there is nothing wrong with that.&lt;/p&gt;

&lt;p&gt;However, any client dependency can lead to a future case of the notorious "DLL hell" problem. While &lt;em&gt;multiple&lt;/em&gt; major versions of the same package are transitively referenced by the root application, we have exactly &lt;em&gt;one&lt;/em&gt; package version bound at runtime.  Then, when we call the other version transitively, we can get a nasty runtime error. In general form, it is unsolvable on the root application side. More technical details can be found, for example, in this thread and its follow-ups: &lt;a href="https://github.com/NuGet/Home/issues/6693" rel="noopener noreferrer"&gt;Referencing multiple package versions within one project with extern aliases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Things often get ugly in a service that implements &lt;a href="https://microservices.io/patterns/data/saga.html" rel="noopener noreferrer"&gt;Saga&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Facade_pattern" rel="noopener noreferrer"&gt;Facade&lt;/a&gt; patterns:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd85w68h8n7vzqh39652l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd85w68h8n7vzqh39652l.png" alt="DLL hell" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, you could just update all your (N+1) typed clients at once in case of a breaking change in the core library, but this approach doesn't scale well. &lt;/p&gt;

&lt;p&gt;Again, avoid external dependencies unless it absolutely necessary, and think twice before adding anything to the library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this post, I described a scalable architectural approach to building typed HTTP API clients on top of ASP.NET Core applications. I outlined a number of practices that contribute to API testability and maintainability. I advocated the use of NuGet packages over autogenerated client code. I also talked about customizing the HTTP handler pipeline, adding client-side logic, and avoiding the "DLL hell" problem.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aspnet</category>
      <category>csharp</category>
      <category>http</category>
    </item>
    <item>
      <title>Nested validation in .NET</title>
      <dc:creator>Ilya</dc:creator>
      <pubDate>Wed, 03 Jul 2024 08:39:29 +0000</pubDate>
      <link>https://forem.com/ilya-chumakov/nested-validation-in-net-3j6j</link>
      <guid>https://forem.com/ilya-chumakov/nested-validation-in-net-3j6j</guid>
      <description>&lt;p&gt;In this blog's opening post, I discuss the problem of validating nested  Data Transfer Objects in modern .NET. Nesting simply means that the root object can reference other DTOs, which in turn can reference others and so on, potentially forming a &lt;em&gt;cyclic graph&lt;/em&gt; of unknown size. For each node in the graph, its data properties are validated against a quite typical rule set: nullability, range, length, regular expressions etc. &lt;br&gt;
And for DTO types, let's declare the following conventions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It &lt;em&gt;may&lt;/em&gt; have DataAnnotation attributes, including custom ones.&lt;/li&gt;
&lt;li&gt;It &lt;em&gt;may&lt;/em&gt; implement IValidatableObject.&lt;/li&gt;
&lt;li&gt;It &lt;em&gt;should&lt;/em&gt; avoid third-party dependencies if possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may have guessed that the &lt;em&gt;graph&lt;/em&gt; is the tricky part. Indeed, a built-in &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validator" rel="noopener noreferrer"&gt;DataAnnotations.Validator&lt;/a&gt; doesn't do nested validation by design, and this was a default behaviour &lt;a href="https://stackoverflow.com/questions/2493800/how-can-i-tell-the-data-annotations-validator-to-also-validate-complex-child-pro" rel="noopener noreferrer"&gt;for decades&lt;/a&gt;. But the fix is trivial, right? Just implement any kind of graph traversal with cycle detection! Well, yes and no. In this post, I compare popular third-party libraries that support nested validation. Looking ahead, there is a big performance difference even among robust production-ready solutions. &lt;/p&gt;

&lt;p&gt;There are many ways to define validation rules in .NET, each with its own advantages and disadvantages. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Attributes&lt;/strong&gt;: explicit, useful for OpenAPI document generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IValidatableObject&lt;/strong&gt;: more flexible yet still self-contained.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External&lt;/strong&gt;: This is a jack of all trades. It leaves DTOs clean and provides maximum flexibility (&lt;a href="https://github.com/FluentValidation/FluentValidation" rel="noopener noreferrer"&gt;FluentValidation&lt;/a&gt; is the best example of this approach).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual validation&lt;/strong&gt;: the most naive approach, it simply has inlined &lt;code&gt;if&lt;/code&gt; clauses without declaring validation rules at all. As a result, it gives unbeatable performance at the cost of scalability, and it doesn't apply to a graph of unknown length/topology. Later it is used as a benchmark baseline. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To finish this long intro and save everyone's time, let me highlight what is &lt;em&gt;not covered&lt;/em&gt; in this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation" rel="noopener noreferrer"&gt;ASP.NET Model Validation&lt;/a&gt;. Although it comes with full support for DataAnnotations attributes, it is still an inseparable part of a large and complex framework that deals with both server-side application and Web APIs, ModelState, version backward comparability, etc... a topic that undoubtedly deserves its own article.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IOptions&amp;lt;T&amp;gt;&lt;/code&gt; validation. Ironically, with the &lt;a href="https://github.com/dotnet/runtime/pull/90275" rel="noopener noreferrer"&gt;arrival&lt;/a&gt; of &lt;code&gt;[ValidateObjectMembers]&lt;/code&gt; and &lt;code&gt;[ValidateEnumeratedItems]&lt;/code&gt; in .NET 8, &lt;code&gt;OptionsBuilder&amp;lt;TOptions&amp;gt;&lt;/code&gt; now supports validation of nested options. And there are now at least 3 different validation algorithms shipped with ASP.NET.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What is validation?
&lt;/h3&gt;

&lt;p&gt;Let's say we're processing a user's registration email address. What should we check?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The address should be in the correct format. This is &lt;em&gt;validation&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The address domain should not be on our blacklist. This is a &lt;em&gt;business rule&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The address should be unique in our database. This is a &lt;em&gt;business rule&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What is the difference? Validation is a &lt;em&gt;pure function&lt;/em&gt;. It is deterministic (same input - same output) and has no side effects. That's why looking for a domain in a list is not validation: such lists are subject to change, so they're not deterministic. A good rule of thumb for mere enterprise developers like me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt;: self-contained (we only need the data from the DTO itself)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business rule&lt;/strong&gt;: anything that touches mutable data (database, API, file system etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And my advice is: don't mix them up. Validate your input before the control flow even reaches your business domain. Just like ASP.NET does with model binding. Regardless of the application architecture, in many cases you actually &lt;em&gt;want&lt;/em&gt; fail fast on invalid/malicious input and avoid unnecessary allocation of your scoped and transient services. Then, testing: covering pure functions with tests is trivial. Well, at least it is way easier to do separately, than mocking a database and couple of APIs for all-at-once validator. Put some effort into the quality of the data coming into your domain, and you'll get a clearer and more concise domain logic.&lt;/p&gt;

&lt;p&gt;To go deeper, please read Mark Seemann's &lt;a href="https://blog.ploeh.dk/2023/06/26/validation-and-business-rules/" rel="noopener noreferrer"&gt;Validation and business rules&lt;/a&gt; post, discussing the topic in great detail. Let me say a few things about the libraries under consideration, and we can finally get on with the benchmarking.&lt;/p&gt;
&lt;h3&gt;
  
  
  DataAnnotationsValidator
&lt;/h3&gt;

&lt;p&gt;Our first contender is the &lt;a href="https://www.nuget.org/packages/DataAnnotationsValidator.NETCore" rel="noopener noreferrer"&gt;DataAnnotationsValidator.NETCore&lt;/a&gt; package. It is long dead and has performance issues, so &lt;strong&gt;strongly not recommended&lt;/strong&gt;. However, this library &lt;a href="https://github.com/ovation22/DataAnnotationsValidatorRecursive/blob/master/DataAnnotationsValidator/DataAnnotationsValidator/DataAnnotationsValidator.cs#L51" rel="noopener noreferrer"&gt;illustrates&lt;/a&gt; well the idea behind many home-made solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reflection to read metadata.&lt;/li&gt;
&lt;li&gt;Recursive depth-first search for traversing a graph.&lt;/li&gt;
&lt;li&gt;A hash set for cycle detection.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  MiniValidation
&lt;/h3&gt;

&lt;p&gt;Alive and well-designed, &lt;a href="https://github.com/DamianEdwards/MiniValidation" rel="noopener noreferrer"&gt;MiniValidation&lt;/a&gt; offers smooth experience in nested validation. While implementing a similar depth-first search for visiting a DTO graph, it adds &lt;a href="https://github.com/DamianEdwards/MiniValidation/blob/main/src/MiniValidation/MiniValidator.cs#L385" rel="noopener noreferrer"&gt;metadata caching&lt;/a&gt; to the mix, resulting in much better performance.&lt;/p&gt;
&lt;h3&gt;
  
  
  FluentValidation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/FluentValidation/FluentValidation" rel="noopener noreferrer"&gt;FluentValidation&lt;/a&gt; is undoubtedly the most popular third-party validation library on .NET. It is a robust choice if you need clean POCOs or multiple validation maps per type. However, its performance may surprise you.&lt;/p&gt;
&lt;h3&gt;
  
  
  Benchmark: DataAnnotation and FluentValidation
&lt;/h3&gt;

&lt;p&gt;Our first benchmark is to validate a fairly typical DataAnnotation-marked DTO, containing both a single nested object and a collection of them (each is expected to be validated):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AllowEmptyStrings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StringLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MinimumLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Children&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IChild&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ChildCreatedAt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;AllowedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ChildFlag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, FluentValidation has no use for these attributes, so its validators are created separately while repeating the same rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParentValidator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AbstractValidator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ParentValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;InclusiveBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;NotEmpty&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;NotNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;SetValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChildValidator&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nf"&gt;RuleForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Children&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;NotNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;SetValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChildValidator&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChildValidator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AbstractValidator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChildValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChildCreatedAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;NotNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChildFlag&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the &lt;code&gt;Manual&lt;/code&gt; benchmark uses explicit &lt;code&gt;if&lt;/code&gt; checks and serves as a baseline. Each benchmark is runned against of &lt;em&gt;the same&lt;/em&gt; &lt;code&gt;Parent&lt;/code&gt; collection. There are the results depending on the collection size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Allocated&lt;/th&gt;
&lt;th&gt;Alloc Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3 μs&lt;/td&gt;
&lt;td&gt;34 KB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MiniValidation&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;162 μs&lt;/td&gt;
&lt;td&gt;427 KB&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataAnnotationsValidator&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;302 μs&lt;/td&gt;
&lt;td&gt;614 KB&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FluentValidation&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;314 μs&lt;/td&gt;
&lt;td&gt;946 KB&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;33 μs&lt;/td&gt;
&lt;td&gt;343 KB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MiniValidation&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;1586 μs&lt;/td&gt;
&lt;td&gt;4260 KB&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataAnnotationsValidator&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;3084 μs&lt;/td&gt;
&lt;td&gt;6150 KB&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FluentValidation&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;3300 μs&lt;/td&gt;
&lt;td&gt;9586 KB&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;342 μs&lt;/td&gt;
&lt;td&gt;3437 KB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MiniValidation&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;16237 μs&lt;/td&gt;
&lt;td&gt;42619 KB&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataAnnotationsValidator&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;31223 μs&lt;/td&gt;
&lt;td&gt;61480 KB&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FluentValidation&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;32364 μs&lt;/td&gt;
&lt;td&gt;95911 KB&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Well, DataAnnotationsValidator is expectedly bad, but FluentValidation... is even worse in both time and space! At first I thought there was a bug (there was not). Then I did my best to look for FluentValidation settings that might help to optimise its performance (there weren't any, except "fail fast", see below). The overall result distribution remains the same. &lt;br&gt;
But look at MiniValidation! The same algorithm, but optimised for performance, gives a quite impressive 2x boost over DataAnnotationsValidator.&lt;/p&gt;
&lt;h3&gt;
  
  
  Benchmark: IValidatableObject
&lt;/h3&gt;

&lt;p&gt;As you probably know, &lt;code&gt;IValidatableObject&lt;/code&gt; is an alternative to explicit DataAnnotations attributes, with all the validation logic encapsulated within DTOs. This benchmark uses the same validation rules but implemented in &lt;code&gt;Validate&lt;/code&gt; method, so it's all about traversing a graph and calling &lt;code&gt;Validate&lt;/code&gt; at each node. FluentValidation is not on the list this time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChildValidatableObject&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IValidatableObject&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ChildCreatedAt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ChildFlag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ValidationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationContext&lt;/span&gt; &lt;span class="n"&gt;validationContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChildCreatedAt&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ValidationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo error message #2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChildCreatedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChildFlag&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ValidationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo error message #3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChildFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Allocated&lt;/th&gt;
&lt;th&gt;Alloc Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;'Manual with IVO.Validate call'&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;21 μs&lt;/td&gt;
&lt;td&gt;109 KB&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'MiniValidation + IVO'&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;59 μs&lt;/td&gt;
&lt;td&gt;199 KB&lt;/td&gt;
&lt;td&gt;1.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'DataAnnotationsValidator + IVO'&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;151 μs&lt;/td&gt;
&lt;td&gt;442 KB&lt;/td&gt;
&lt;td&gt;4.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Manual with IVO.Validate call'&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;206 μs&lt;/td&gt;
&lt;td&gt;1093 KB&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'MiniValidation + IVO'&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;565 μs&lt;/td&gt;
&lt;td&gt;1992 KB&lt;/td&gt;
&lt;td&gt;1.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'DataAnnotationsValidator + IVO'&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;1511 μs&lt;/td&gt;
&lt;td&gt;4421 KB&lt;/td&gt;
&lt;td&gt;4.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Manual with IVO.Validate call'&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2141 μs&lt;/td&gt;
&lt;td&gt;10937 KB&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'MiniValidation + IVO'&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;6608 μs&lt;/td&gt;
&lt;td&gt;19921 KB&lt;/td&gt;
&lt;td&gt;1.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'DataAnnotationsValidator + IVO'&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;16254 μs&lt;/td&gt;
&lt;td&gt;44219 KB&lt;/td&gt;
&lt;td&gt;4.04&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Again, MiniValidation wins by an even larger margin. Now let's merge the results and look at the overall performance (values rounded for readability):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Allocated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;342 μs&lt;/td&gt;
&lt;td&gt;3437 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual with IVO.Validate call&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;2141 μs&lt;/td&gt;
&lt;td&gt;10937 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MiniValidation + IVO&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;6608 μs&lt;/td&gt;
&lt;td&gt;19921 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MiniValidation&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;16237 μs&lt;/td&gt;
&lt;td&gt;42619 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataAnnotationsValidator + IVO&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;16254 μs&lt;/td&gt;
&lt;td&gt;44219 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataAnnotationsValidator&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;31223 μs&lt;/td&gt;
&lt;td&gt;61480 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FluentValidation&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;32364 μs&lt;/td&gt;
&lt;td&gt;95912 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You may notice that MiniValidation + &lt;code&gt;IValidatableObject&lt;/code&gt; give the best results of all third party libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark: Fail fast
&lt;/h3&gt;

&lt;p&gt;And yet FluentValidation has the feature that other competitors lack: &lt;a href="https://docs.fluentvalidation.net/en/latest/cascade.html#validator-class-level-cascade-modes" rel="noopener noreferrer"&gt;CascadeMode.Stop&lt;/a&gt;. It's flexible and can be set at different levels (rule, class, global):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FailfastChildValidator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AbstractValidator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;FailfastChildValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ClassLevelCascadeMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CascadeMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;//All the rules are declared as usual&lt;/span&gt;
        &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Allocated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FluentValidation + Fail Fast&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;9012 μs&lt;/td&gt;
&lt;td&gt;38556 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FluentValidation&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;32364 μs&lt;/td&gt;
&lt;td&gt;95911 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Of course, the fail-fast version is much faster. Most of the time I prefer the full validation report, but fail-fast is an option worth mentioning when talking about performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this post I discussed the problem of validating nested objects in .NET. Since the built-in DataAnnotations validator doesn't traverse complex properties, we have to rely on third-party libraries for this. I explained the difference between validation and business rules, and why this is important.&lt;/p&gt;

&lt;p&gt;As for the benchmark results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The MiniValidation library shows the best overall performance.&lt;/li&gt;
&lt;li&gt;FluentValidation, despite its popularity, is generally 2x slower. There are some faster alternatives, such as &lt;a href="https://github.com/bartoszlenar/Validot" rel="noopener noreferrer"&gt;Validot&lt;/a&gt;, but I would like to leave the burden of benchmarking to its maintainers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And don't get me wrong. If you want to decouple your rules from DTOs and get a simple, stable and production-tested solution - just take FluentValidation, because its performance difference is negligible in many cases. If you need self-describing DTOs - stick with MiniValidation. And for performance driven code - inline your checks where possible.&lt;/p&gt;

&lt;p&gt;The obvious next step in the development of general purpose validation libraries, is, of course, the adoption of &lt;del&gt;ChatGPT&lt;/del&gt; source generators. A validation generator such as &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/options-validation-generator" rel="noopener noreferrer"&gt;this one&lt;/a&gt; would potentially eliminate the performance gap between general usage validation libraries and inlined validation. In fact, we already have all the necessary technology shipped with .NET, so stay tuned for news!&lt;/p&gt;

&lt;p&gt;All the code from the article is available on Github: &lt;a href="https://github.com/ilya-chumakov/PaperSource.DtoGraphValidation" rel="noopener noreferrer"&gt;https://github.com/ilya-chumakov/PaperSource.DtoGraphValidation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>dotnetcore</category>
    </item>
  </channel>
</rss>
