<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Berk Selvi | Software Developer</title><link>https://berkselvi.dev/</link><description>Recent content on Berk Selvi | Software Developer</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>Made by Berk Selvi. &lt;br&gt; This site is &lt;a href='https://github.com/berkslv/berkselvi.dev' target='_blank'&gt;open source&lt;/a&gt;</copyright><lastBuildDate>Sun, 02 Aug 2026 00:00:00 +0300</lastBuildDate><atom:link href="https://berkselvi.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>From IEnumerable to IAsyncEnumerable: Streaming Large Datasets in .NET Without Killing Memory</title><link>https://berkselvi.dev/posts/from-ienumerable-to-iasyncenumerable-streaming-large-datasets-in-dotnet-without-killing-memory/</link><pubDate>Sun, 02 Aug 2026 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/from-ienumerable-to-iasyncenumerable-streaming-large-datasets-in-dotnet-without-killing-memory/</guid><description>&lt;p&gt;A few months ago, one of our nightly export jobs started throwing &lt;code&gt;OutOfMemoryException&lt;/code&gt;. Nothing in the code had changed. What had changed was the size of the table it was reading from—it had quietly grown from 200K rows to 4 million. The job was doing exactly what we told it to do: &lt;code&gt;.ToListAsync()&lt;/code&gt; the whole thing, then loop over it. It just took the whole database table into RAM first, and RAM lost.&lt;/p&gt;</description><content>&lt;p&gt;A few months ago, one of our nightly export jobs started throwing &lt;code&gt;OutOfMemoryException&lt;/code&gt;. Nothing in the code had changed. What had changed was the size of the table it was reading from—it had quietly grown from 200K rows to 4 million. The job was doing exactly what we told it to do: &lt;code&gt;.ToListAsync()&lt;/code&gt; the whole thing, then loop over it. It just took the whole database table into RAM first, and RAM lost.&lt;/p&gt;
&lt;p&gt;This is a trap almost every .NET developer falls into at some point: reaching for &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; (or its async cousin &lt;code&gt;Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;) when what you actually need is &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; streaming large datasets record by record. &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; look similar on the surface, but they solve very different problems, and picking the wrong one is exactly how you end up paging through a 4-million-row table in memory at 2am.&lt;/p&gt;
&lt;h2 id="ienumerable-vs-iasyncenumerable-at-a-glance"&gt;IEnumerable vs IAsyncEnumerable at a Glance&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;&lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; / &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Materialization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully realized before iteration starts&lt;/td&gt;
&lt;td&gt;Pulled one item at a time, on demand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Grows with result set size&lt;/td&gt;
&lt;td&gt;Roughly constant, regardless of size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to first item&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Waits for the entire query/collection&lt;/td&gt;
&lt;td&gt;First item available as soon as it&amp;rsquo;s ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cancellation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Only stops your loop; work already done stays done&lt;/td&gt;
&lt;td&gt;Can stop the underlying query mid-stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Iteration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Repeatable, indexable&lt;/td&gt;
&lt;td&gt;Forward-only, single-pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typical use&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small, bounded collections&lt;/td&gt;
&lt;td&gt;Exports, ETL, paginated APIs, log processing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="the-old-way-load-everything-then-iterate"&gt;The Old Way: Load Everything, Then Iterate&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the pattern that got us into trouble. A typical .NET service reading orders to export them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;List&amp;lt;Order&amp;gt;&amp;gt; GetAllOrdersAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _dbContext.Orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsNoTracking()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .ToListAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ExportOrdersAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; orders = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; GetAllOrdersAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; orders)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _exportWriter.WriteLineAsync(order.ToCsvRow());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This looks perfectly reasonable, and for small tables it is. But look closely at what &lt;code&gt;ToListAsync()&lt;/code&gt; actually does: it materializes &lt;strong&gt;every single row&lt;/strong&gt; returned by the query into a &lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt; sitting in memory before your code ever touches the first item. If the query returns 4 million rows, you need enough memory to hold 4 million &lt;code&gt;Order&lt;/code&gt; objects simultaneously, even though you only ever process one row at a time in the &lt;code&gt;foreach&lt;/code&gt; loop.&lt;/p&gt;
&lt;p&gt;The symptoms creep up slowly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Memory spikes:&lt;/strong&gt; GC pressure increases, the process&amp;rsquo;s working set balloons, and container memory limits get hit in Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency before the first result:&lt;/strong&gt; The caller waits for the &lt;em&gt;entire&lt;/em&gt; query to complete before seeing a single row, even if all they wanted was to stream results to a client.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wasted work on cancellation:&lt;/strong&gt; If the caller aborts halfway through, you&amp;rsquo;ve already paid the full cost of loading everything.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No backpressure:&lt;/strong&gt; There&amp;rsquo;s no way to say &amp;ldquo;slow down, I&amp;rsquo;m still processing the last batch&amp;rdquo;—the database just keeps producing rows as fast as the driver can buffer them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a background job doing an export, an API endpoint streaming a CSV, or a data migration script, this pattern doesn&amp;rsquo;t scale. It works in local development with your 50-row seed dataset, and quietly breaks in production.&lt;/p&gt;
&lt;h2 id="why-iasyncenumerable"&gt;Why IAsyncEnumerable?&lt;/h2&gt;
&lt;p&gt;.NET Core 3.0 introduced &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;, paired with the &lt;code&gt;await foreach&lt;/code&gt; syntax, specifically to solve this class of problem. Instead of a method returning a fully realized collection, it returns a sequence that is &lt;strong&gt;pulled&lt;/strong&gt; one item at a time, asynchronously, as the consumer asks for the next element.&lt;/p&gt;
&lt;p&gt;The mental model shift is important:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;&lt;/strong&gt; means &amp;ldquo;wait for everything to be ready, then give me the whole thing.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/strong&gt; means &amp;ldquo;give me an item whenever I ask for the next one, and let me &lt;code&gt;await&lt;/code&gt; if it&amp;rsquo;s not ready yet.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This unlocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Constant memory usage:&lt;/strong&gt; Only one item (or a small buffer) needs to be in memory at any given time, regardless of whether the source has 10 rows or 10 million.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time-to-first-byte improvements:&lt;/strong&gt; A consumer can start processing (or an HTTP client can start receiving) data as soon as the first row is available, instead of waiting for the full result set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Natural cancellation support:&lt;/strong&gt; &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; composes with &lt;code&gt;CancellationToken&lt;/code&gt; via &lt;code&gt;WithCancellation&lt;/code&gt;, so aborting a stream actually stops the underlying query instead of just discarding an already-loaded list.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composability with LINQ:&lt;/strong&gt; With the &lt;code&gt;System.Linq.Async&lt;/code&gt; package, you get &lt;code&gt;Select&lt;/code&gt;, &lt;code&gt;Where&lt;/code&gt;, and friends over async streams, so you don&amp;rsquo;t lose the expressiveness you&amp;rsquo;re used to with &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The trade-off is that you give up random access and the ability to know the count upfront without a separate query—&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; is forward-only, single-pass, by design. For streaming scenarios, that&amp;rsquo;s exactly the right constraint.&lt;/p&gt;
&lt;h2 id="producing-an-iasyncenumerable-with-yield-return"&gt;Producing an IAsyncEnumerable with yield return&lt;/h2&gt;
&lt;p&gt;The simplest way to produce an &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; is with &lt;code&gt;yield return&lt;/code&gt; inside an &lt;code&gt;async&lt;/code&gt; method. Here&amp;rsquo;s a hand-rolled example that streams numbers with a simulated I/O delay, useful for understanding the mechanics before we bring EF Core into the picture:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; IAsyncEnumerable&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; GenerateNumbersAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; count,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [EnumeratorCancellation]&lt;/span&gt; CancellationToken cancellationToken = &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &amp;lt; count; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cancellationToken.ThrowIfCancellationRequested();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Simulate an async I/O operation, e.g. a network call per item&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Task.Delay(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;yield&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;[EnumeratorCancellation]&lt;/code&gt; attribute is easy to miss but important: without it, a &lt;code&gt;CancellationToken&lt;/code&gt; passed to &lt;code&gt;WithCancellation&lt;/code&gt; won&amp;rsquo;t actually flow into the method body, and &lt;code&gt;ThrowIfCancellationRequested&lt;/code&gt; becomes a no-op against the caller&amp;rsquo;s token.&lt;/p&gt;
&lt;p&gt;Consuming it looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; number &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; GenerateNumbersAsync(&lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;, cts.Token))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(number);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice what&amp;rsquo;s &lt;em&gt;not&lt;/em&gt; happening here: there&amp;rsquo;s no intermediate &lt;code&gt;List&amp;lt;int&amp;gt;&lt;/code&gt; holding all 1000 numbers. Each &lt;code&gt;number&lt;/code&gt; is produced, consumed, and can be garbage collected before the next one is even generated.&lt;/p&gt;
&lt;h2 id="real-life-example-streaming-ef-core-query-results"&gt;Real-life Example: Streaming EF Core Query Results&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s go back to the export job that paged 4 million &lt;code&gt;Order&lt;/code&gt; rows into memory at 2am and fix it properly. Entity Framework Core exposes &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; directly on &lt;code&gt;DbSet&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;IQueryable&amp;lt;T&amp;gt;&lt;/code&gt; via &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt;, so you don&amp;rsquo;t need to write your own &lt;code&gt;yield return&lt;/code&gt; wrapper for database queries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IAsyncEnumerable&amp;lt;Order&amp;gt; StreamOrdersAsync(CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _dbContext.Orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsNoTracking()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .OrderBy(o =&amp;gt; o.Id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsAsyncEnumerable();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ExportOrdersAsync(CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; StreamOrdersAsync(cancellationToken).WithCancellation(cancellationToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _exportWriter.WriteLineAsync(order.ToCsvRow());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The difference is subtle in the code but massive at runtime. Instead of the query executing fully and buffering 4 million &lt;code&gt;Order&lt;/code&gt; rows into a &lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt;, EF Core keeps the underlying &lt;code&gt;DbDataReader&lt;/code&gt; open and streams rows from the database connection as the &lt;code&gt;await foreach&lt;/code&gt; loop asks for them. Memory usage for the query itself stays roughly flat regardless of table size—you&amp;rsquo;re paying for one row (plus a small internal buffer) at a time, not the whole result set.&lt;/p&gt;
&lt;p&gt;Two details matter here that are easy to miss. First, &lt;code&gt;.AsNoTracking()&lt;/code&gt; isn&amp;rsquo;t decorative—without it, EF Core&amp;rsquo;s change tracker keeps a snapshot of every entity it hands you for the lifetime of the &lt;code&gt;DbContext&lt;/code&gt;, which quietly reintroduces the exact memory growth you&amp;rsquo;re trying to eliminate, even though the query itself is streaming. Second, unlike the hand-rolled &lt;code&gt;yield return&lt;/code&gt; example above, &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt; doesn&amp;rsquo;t require you to add &lt;code&gt;[EnumeratorCancellation]&lt;/code&gt; anywhere in your own code—EF Core wires the &lt;code&gt;CancellationToken&lt;/code&gt; you pass to &lt;code&gt;WithCancellation&lt;/code&gt; into its own query pipeline internally. The attribute only matters when you&amp;rsquo;re writing the &lt;code&gt;async IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; method yourself.&lt;/p&gt;
&lt;p&gt;This also composes cleanly with ASP.NET Core. If you expose this as a minimal API endpoint, the framework will serialize the stream directly to the response body as items become available, instead of buffering the whole JSON array server-side first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGet(&lt;span style="color:#e6db74"&gt;&amp;#34;/orders/stream&amp;#34;&lt;/span&gt;, (AppDbContext dbContext, CancellationToken cancellationToken) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; dbContext.Orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsNoTracking()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .OrderBy(o =&amp;gt; o.Id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsAsyncEnumerable();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since .NET 6, ASP.NET Core&amp;rsquo;s minimal APIs detect the &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; return type and stream the JSON array chunk by chunk as the underlying enumerable produces items, rather than requiring you to materialize the full list before returning it. One gotcha worth knowing: if response compression middleware sits in front of the endpoint, it can end up buffering the response internally to compress it effectively, quietly defeating the streaming behavior you just wired up—worth checking with a real client and &lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt; before assuming it&amp;rsquo;s working end to end.&lt;/p&gt;
&lt;h3 id="a-note-on-batching"&gt;A Note on Batching&lt;/h3&gt;
&lt;p&gt;Streaming one row at a time from EF Core is memory-friendly, but if you&amp;rsquo;re writing each row to an external system (an HTTP call, a message queue, a second database), doing that per-row can be slow due to per-item overhead. A common middle ground is to batch the stream into chunks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; IAsyncEnumerable&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; ChunkAsync&amp;lt;T&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; IAsyncEnumerable&amp;lt;T&amp;gt; source,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; chunkSize,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [EnumeratorCancellation]&lt;/span&gt; CancellationToken cancellationToken = &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; buffer = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;T&amp;gt;(chunkSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; item &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; source.WithCancellation(cancellationToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; buffer.Add(item);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (buffer.Count == chunkSize)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;yield&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; buffer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; buffer = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;T&amp;gt;(chunkSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (buffer.Count &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;yield&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; buffer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Used like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; batch &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; StreamOrdersAsync(cancellationToken).ChunkAsync(&lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;, cancellationToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _messageBus.PublishBatchAsync(batch);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You still keep memory bounded—only 500 orders in flight at a time instead of 4 million—while amortizing the per-call overhead of the downstream system across a batch.&lt;/p&gt;
&lt;h2 id="when-iasyncenumerable-isnt-the-right-tool"&gt;When IAsyncEnumerable Isn&amp;rsquo;t the Right Tool&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; isn&amp;rsquo;t a universal replacement for &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;. It&amp;rsquo;s the wrong choice when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need the count, need to sort after loading, or need random access (e.g., &lt;code&gt;orders[42]&lt;/code&gt;)—those require materializing the data anyway.&lt;/li&gt;
&lt;li&gt;The dataset is genuinely small and bounded (a dropdown list of 20 categories doesn&amp;rsquo;t need streaming semantics).&lt;/li&gt;
&lt;li&gt;You need to iterate the same sequence multiple times—&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; is single-pass by convention; re-enumerating typically re-runs the underlying query.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re doing in-memory transformations that need the whole collection anyway (e.g., computing a median), in which case streaming just adds &lt;code&gt;await&lt;/code&gt; overhead without reducing memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use it specifically when the shape of the problem is &amp;ldquo;process a potentially large sequence, one item (or small batch) at a time, without needing to hold it all at once.&amp;rdquo; Exports, ETL pipelines, paginated API results, log processing, and large report generation are the classic cases.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;OutOfMemoryException&lt;/code&gt; that started this whole investigation for us wasn&amp;rsquo;t a bug in the traditional sense—the code was doing exactly what &lt;code&gt;ToListAsync()&lt;/code&gt; promises to do. The real fix wasn&amp;rsquo;t a smarter allocation strategy or a bigger container memory limit; it was recognizing that the job never needed a &lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt; sitting in memory in the first place. It needed a stream. Once we swapped &lt;code&gt;ToListAsync()&lt;/code&gt; for &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt; on that export job, the same 4-million-row table processed with flat, predictable memory usage, and the &lt;code&gt;OutOfMemoryException&lt;/code&gt; never came back.&lt;/p&gt;
&lt;p&gt;Streaming large datasets with &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; won&amp;rsquo;t make every query faster, and it does add complexity—cancellation tokens, &lt;code&gt;[EnumeratorCancellation]&lt;/code&gt;, and the loss of random access and repeatable iteration aren&amp;rsquo;t free. But when your problem is genuinely &amp;ldquo;read a lot of data and process it sequentially,&amp;rdquo; &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; turns an unbounded memory problem into a constant one. The next time you write &lt;code&gt;.ToListAsync()&lt;/code&gt; out of habit on a query that could return more rows than you expect, it&amp;rsquo;s worth pausing and asking whether you actually need the list, or whether you just need the next item.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thanks for reading! 🎉 To not miss my research in software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.
&lt;/content&gt;&lt;/p&gt;</content></item><item><title>The Idempotency Key Pattern in .NET: What Happens When Your Payment API Gets Called Twice</title><link>https://berkselvi.dev/posts/the-idempotency-key-pattern-in-dotnet-what-happens-when-your-payment-api-gets-called-twice/</link><pubDate>Wed, 22 Jul 2026 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/the-idempotency-key-pattern-in-dotnet-what-happens-when-your-payment-api-gets-called-twice/</guid><description>&lt;p&gt;A customer clicks &amp;ldquo;Pay Now.&amp;rdquo; The request hits your API, your payment provider charges the card, and then&amp;hellip; the response times out. The mobile app doesn&amp;rsquo;t know if the payment succeeded, so it does the only thing it knows how to do: it retries. Now you have two successful charges for one order, an angry customer, and a support ticket that starts with &amp;ldquo;I was charged twice.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve built anything that touches money, queues, or third-party APIs, you already know this scenario isn&amp;rsquo;t hypothetical. Network blips, client timeouts, and load balancer retries are a fact of life in distributed systems. The &lt;strong&gt;idempotency key pattern&lt;/strong&gt; is how production-grade payment APIs guarantee that the same logical request, no matter how many times it&amp;rsquo;s retried, only ever gets applied once. In this post, I&amp;rsquo;ll walk through why this matters, what typically goes wrong without it, and how to implement it properly in a .NET API.&lt;/p&gt;</description><content>&lt;p&gt;A customer clicks &amp;ldquo;Pay Now.&amp;rdquo; The request hits your API, your payment provider charges the card, and then&amp;hellip; the response times out. The mobile app doesn&amp;rsquo;t know if the payment succeeded, so it does the only thing it knows how to do: it retries. Now you have two successful charges for one order, an angry customer, and a support ticket that starts with &amp;ldquo;I was charged twice.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve built anything that touches money, queues, or third-party APIs, you already know this scenario isn&amp;rsquo;t hypothetical. Network blips, client timeouts, and load balancer retries are a fact of life in distributed systems. The &lt;strong&gt;idempotency key pattern&lt;/strong&gt; is how production-grade payment APIs guarantee that the same logical request, no matter how many times it&amp;rsquo;s retried, only ever gets applied once. In this post, I&amp;rsquo;ll walk through why this matters, what typically goes wrong without it, and how to implement it properly in a .NET API.&lt;/p&gt;
&lt;h2 id="the-problem-at-least-once-delivery-is-the-default"&gt;The Problem: &amp;ldquo;At-Least-Once&amp;rdquo; Delivery Is the Default&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s an uncomfortable truth: over an unreliable network, you cannot guarantee &amp;ldquo;exactly-once&amp;rdquo; delivery of a request. TCP connections drop mid-response, HTTP clients time out, and load balancers or mobile apps retry on 5xx errors and timeouts. The best you can realistically build for is &lt;strong&gt;at-least-once delivery&lt;/strong&gt;, meaning your endpoint might receive the exact same logical request two, three, or ten times.&lt;/p&gt;
&lt;p&gt;For a &lt;code&gt;GET /orders/123&lt;/code&gt; request, that&amp;rsquo;s harmless. Reading data twice changes nothing. But for &lt;code&gt;POST /payments/charge&lt;/code&gt;, every one of those retries can trigger a brand-new charge, because from the server&amp;rsquo;s point of view, it just looks like a new, unrelated request.&lt;/p&gt;
&lt;h2 id="the-old-way-no-protection-against-duplicate-requests"&gt;The Old Way: No Protection Against Duplicate Requests&lt;/h2&gt;
&lt;p&gt;Most teams start without any protection at all. A typical naive payment endpoint looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[HttpPost(&amp;#34;charge&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;IActionResult&amp;gt; ChargeCard([FromBody] ChargeRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; result = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _paymentGateway.ChargeAsync(request.CardToken, request.Amount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _orderRepository.MarkAsPaidAsync(request.OrderId, result.TransactionId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(result);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This works fine in the happy path. But walk through what happens when the client times out waiting for a response that was actually about to succeed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client sends &lt;code&gt;POST /payments/charge&lt;/code&gt; for order &lt;code&gt;#4711&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Server calls the payment gateway, the card is charged successfully.&lt;/li&gt;
&lt;li&gt;Before the server can respond, the client&amp;rsquo;s HTTP timeout fires.&lt;/li&gt;
&lt;li&gt;The client, following standard retry logic, sends the exact same request again.&lt;/li&gt;
&lt;li&gt;The server has no memory of the first attempt, so it charges the card a second time.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No amount of &amp;ldquo;just don&amp;rsquo;t time out&amp;rdquo; fixes this, because timeouts, dropped connections, and process crashes are a normal part of operating a networked service in &lt;strong&gt;microservices&lt;/strong&gt; and other &lt;strong&gt;distributed systems&lt;/strong&gt;. The fix isn&amp;rsquo;t to prevent retries; it&amp;rsquo;s to make retries safe with a properly designed &lt;strong&gt;idempotent API&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="why-idempotency-keys"&gt;Why Idempotency Keys?&lt;/h2&gt;
&lt;p&gt;An &lt;strong&gt;idempotency key&lt;/strong&gt; is a unique identifier, usually a client-generated UUID, that the caller attaches to a request. It tells your API: &amp;ldquo;this specific attempt represents one logical operation, no matter how many times you see it.&amp;rdquo; The server&amp;rsquo;s job is to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Store the result of the first successful request against that key.&lt;/li&gt;
&lt;li&gt;On any duplicate request carrying the same key, return the &lt;strong&gt;stored result&lt;/strong&gt; instead of re-executing the operation.&lt;/li&gt;
&lt;li&gt;Reject requests that reuse a key with a different payload (a sign the client is misusing the key).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="what-makes-an-api-truly-idempotent"&gt;What Makes an API Truly Idempotent&lt;/h3&gt;
&lt;p&gt;Turning a regular endpoint into an idempotent API means the endpoint has to satisfy three properties, regardless of how many times a client calls it with the same key:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single execution of side effects:&lt;/strong&gt; the payment gateway, inventory system, or database write only happens once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent response:&lt;/strong&gt; every retry gets back the exact same response body and status code as the original attempt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bounded lifetime:&lt;/strong&gt; the key eventually expires, so the store doesn&amp;rsquo;t grow forever and legitimate new requests aren&amp;rsquo;t blocked by a key some client reused by mistake.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stripe, one of the most heavily retried APIs on the planet, &lt;a href="https://stripe.com/docs/api/idempotent_requests"&gt;documents this exact pattern&lt;/a&gt; and requires an &lt;code&gt;Idempotency-Key&lt;/code&gt; header on all state-changing requests. It&amp;rsquo;s not a nice-to-have; it&amp;rsquo;s the foundation that makes their retry-heavy SDKs safe to use against a payments API.&lt;/p&gt;
&lt;h2 id="designing-the-idempotency-store"&gt;Designing the Idempotency Store&lt;/h2&gt;
&lt;p&gt;Before writing any middleware, you need a place to persist idempotency records. A simple relational table works well:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; IdempotencyKeys (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;Key&lt;/span&gt; VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RequestHash VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ResponseStatus INT &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ResponseBody TEXT &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status VARCHAR(&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, &lt;span style="color:#75715e"&gt;-- &amp;#39;Processing&amp;#39; | &amp;#39;Completed&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CreatedAt &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ExpiresAt &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A few design decisions matter here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;RequestHash&lt;/code&gt;&lt;/strong&gt;: a hash of the request body, so you can detect if a client reuses the same key for a &lt;em&gt;different&lt;/em&gt; payload (that&amp;rsquo;s a client bug, and you should return a &lt;code&gt;422&lt;/code&gt; instead of silently returning the wrong stored response).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Status&lt;/code&gt;&lt;/strong&gt;: you need a &amp;ldquo;Processing&amp;rdquo; state, not just &amp;ldquo;Completed,&amp;rdquo; so a second concurrent request can tell that the first one is still in flight. The actual guarantee against double-charging comes from the database&amp;rsquo;s unique constraint on &lt;code&gt;Key&lt;/code&gt; rejecting the second insert, not from the &lt;code&gt;Status&lt;/code&gt; column alone — you&amp;rsquo;ll see this in the store implementation below.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ExpiresAt&lt;/code&gt;&lt;/strong&gt;: idempotency keys shouldn&amp;rsquo;t live forever. Stripe expires them after 24 hours; pick a window that matches how long your clients realistically retry, and run a background cleanup job that deletes rows past &lt;code&gt;ExpiresAt&lt;/code&gt; so the table doesn&amp;rsquo;t grow unbounded.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note this schema uses Postgres-flavored SQL (&lt;code&gt;ON CONFLICT&lt;/code&gt;). If you&amp;rsquo;re on SQL Server, the equivalent is &lt;code&gt;MERGE&lt;/code&gt; or an &lt;code&gt;IF NOT EXISTS&lt;/code&gt; guard combined with a unique index on &lt;code&gt;Key&lt;/code&gt; — the concurrency-safety principle is the same, only the syntax differs.&lt;/p&gt;
&lt;h2 id="implementing-it-in-net"&gt;Implementing It in .NET&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s build this as an &lt;code&gt;ActionFilter&lt;/code&gt;, so it can be applied declaratively to any endpoint that needs idempotency protection, without polluting the business logic.&lt;/p&gt;
&lt;h3 id="1-the-idempotency-store"&gt;1. The Idempotency Store&lt;/h3&gt;
&lt;p&gt;This example uses &lt;a href="https://github.com/DapperLib/Dapper"&gt;Dapper&lt;/a&gt; for the raw SQL access, so add it first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Dapper
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IIdempotencyStore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task&amp;lt;IdempotencyRecord?&amp;gt; GetAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, CancellationToken ct);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;&amp;gt; TryStartProcessingAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; requestHash, CancellationToken ct);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task CompleteAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; statusCode, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; responseBody, CancellationToken ct);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IdempotencyRecord&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; RequestHash, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Status, &lt;span style="color:#66d9ef"&gt;int?&lt;/span&gt; ResponseStatus, &lt;span style="color:#66d9ef"&gt;string?&lt;/span&gt; ResponseBody);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;TryStartProcessingAsync&lt;/code&gt; method is the critical piece. It must be an atomic &amp;ldquo;insert if not exists&amp;rdquo; operation, otherwise two concurrent requests with the same key can both pass the check and both proceed to charge the card:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SqlIdempotencyStore&lt;/span&gt; : IIdempotencyStore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IDbConnection _db;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; SqlIdempotencyStore(IDbConnection db) =&amp;gt; _db = db;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;&amp;gt; TryStartProcessingAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; requestHash, CancellationToken ct)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Relies on the PRIMARY KEY constraint on `Key` to fail on duplicates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; sql = &lt;span style="color:#e6db74"&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; INSERT INTO IdempotencyKeys (Key, RequestHash, Status, CreatedAt, ExpiresAt)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; VALUES (@Key, @RequestHash, &amp;#39;Processing&amp;#39;, @Now, @Expires)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ON CONFLICT (Key) DO NOTHING
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; RETURNING Key;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; inserted = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _db.QueryFirstOrDefaultAsync&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt;(sql, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Key = key,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RequestHash = requestHash,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Now = DateTime.UtcNow,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Expires = DateTime.UtcNow.AddHours(&lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; inserted != &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;IdempotencyRecord?&amp;gt; GetAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, CancellationToken ct)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; sql = &lt;span style="color:#e6db74"&gt;&amp;#34;SELECT RequestHash, Status, ResponseStatus, ResponseBody FROM IdempotencyKeys WHERE Key = @Key&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _db.QueryFirstOrDefaultAsync&amp;lt;IdempotencyRecord&amp;gt;(sql, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; { Key = key });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task CompleteAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; statusCode, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; responseBody, CancellationToken ct)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; sql = &lt;span style="color:#e6db74"&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; UPDATE IdempotencyKeys
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SET Status = &amp;#39;Completed&amp;#39;, ResponseStatus = @Status, ResponseBody = @Body
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE Key = @Key&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _db.ExecuteAsync(sql, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; { Key = key, Status = statusCode, Body = responseBody });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;ON CONFLICT (Key) DO NOTHING ... RETURNING Key&lt;/code&gt; trick lets the database&amp;rsquo;s unique constraint do the concurrency control for you, so you don&amp;rsquo;t need application-level locks.&lt;/p&gt;
&lt;h3 id="2-the-action-filter"&gt;2. The Action Filter&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IdempotentAttribute&lt;/span&gt; : Attribute, IAsyncActionFilter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; httpContext = context.HttpContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Let model validation fail first - an invalid request should never consume a key.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!context.ModelState.IsValid)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; next();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!httpContext.Request.Headers.TryGetValue(&lt;span style="color:#e6db74"&gt;&amp;#34;Idempotency-Key&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;out&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; keyHeader) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(keyHeader) || keyHeader.ToString().Length &amp;gt; &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BadRequestObjectResult(&lt;span style="color:#e6db74"&gt;&amp;#34;A valid Idempotency-Key header (max 64 chars) is required.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; store = httpContext.RequestServices.GetRequiredService&amp;lt;IIdempotencyStore&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; key = keyHeader.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; requestHash = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; ComputeRequestHashAsync(httpContext.Request);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; existing = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; store.GetAsync(key, httpContext.RequestAborted);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (existing &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (existing.RequestHash != requestHash)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; UnprocessableEntityObjectResult(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Idempotency-Key was reused with a different request payload.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (existing.Status == &lt;span style="color:#e6db74"&gt;&amp;#34;Completed&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ContentResult
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StatusCode = existing.ResponseStatus,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Content = existing.ResponseBody,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ContentType = &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Still processing - a concurrent duplicate arrived while the first one is in flight.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ConflictObjectResult(&lt;span style="color:#e6db74"&gt;&amp;#34;Request with this Idempotency-Key is already being processed.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; acquired = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; store.TryStartProcessingAsync(key, requestHash, httpContext.RequestAborted);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!acquired)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Lost the race to a concurrent request that inserted first.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ConflictObjectResult(&lt;span style="color:#e6db74"&gt;&amp;#34;Request with this Idempotency-Key is already being processed.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; executedContext = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; next();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Persist whatever the action produced so retries get an identical replay.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Anything other than ObjectResult (exceptions, StatusCodeResult, etc.) should&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// still release the key - otherwise it stays stuck in &amp;#34;Processing&amp;#34; until it expires.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (executedContext.Result &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; ObjectResult objectResult)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; statusCode = objectResult.StatusCode ?? &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; body = JsonSerializer.Serialize(objectResult.Value, httpContext.RequestServices
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .GetRequiredService&amp;lt;IOptions&amp;lt;JsonOptions&amp;gt;&amp;gt;().Value.JsonSerializerOptions);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; store.CompleteAsync(key, statusCode, body, httpContext.RequestAborted);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; statusCode = executedContext.Exception &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; ? &lt;span style="color:#ae81ff"&gt;500&lt;/span&gt; : (httpContext.Response.StatusCode);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; store.CompleteAsync(key, statusCode, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty, httpContext.RequestAborted);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt; ComputeRequestHashAsync(HttpRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request.EnableBuffering();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var reader = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StreamReader(request.Body, leaveOpen: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; body = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; reader.ReadToEndAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request.Body.Position = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; bytes = Encoding.UTF8.GetBytes(body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; hash = SHA256.HashData(bytes);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Convert.ToHexString(hash);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two details worth calling out: the &lt;code&gt;JsonSerializerOptions&lt;/code&gt; is pulled from the app&amp;rsquo;s actual &lt;code&gt;AddJsonOptions&lt;/code&gt; configuration via &lt;code&gt;IOptions&amp;lt;JsonOptions&amp;gt;&lt;/code&gt;, so the replayed body matches the casing and converters the client already expects — reusing the default serializer here would silently produce a different payload than the original response. And every code path, including failures, now calls &lt;code&gt;CompleteAsync&lt;/code&gt; so a key never gets permanently stuck in &lt;code&gt;&amp;quot;Processing&amp;quot;&lt;/code&gt;; combine this with the &lt;code&gt;ExpiresAt&lt;/code&gt; cleanup job so any record that somehow still gets stuck is reclaimed automatically.&lt;/p&gt;
&lt;h3 id="3-applying-the-idempotent-api-to-the-endpoint"&gt;3. Applying the Idempotent API to the Endpoint&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[HttpPost(&amp;#34;charge&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Idempotent]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;IActionResult&amp;gt; ChargeCard([FromBody] ChargeRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; result = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _paymentGateway.ChargeAsync(request.CardToken, request.Amount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _orderRepository.MarkAsPaidAsync(request.OrderId, result.TransactionId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(result);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the client is responsible for generating a UUID per logical payment attempt and sending it as the &lt;code&gt;Idempotency-Key&lt;/code&gt; header on every retry of that same attempt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -X POST https://api.example.com/payments/charge &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Idempotency-Key: 8f14e45f-ceea-467e-bb9d-8f7c1a1c1b1a&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;orderId&amp;#34;:&amp;#34;4711&amp;#34;,&amp;#34;cardToken&amp;#34;:&amp;#34;tok_visa&amp;#34;,&amp;#34;amount&amp;#34;:4999}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the network drops before the response arrives and the client retries with the exact same header, the second request returns the stored result from the first attempt, without ever touching the payment gateway again.&lt;/p&gt;
&lt;h2 id="what-this-doesnt-solve"&gt;What This Doesn&amp;rsquo;t Solve&lt;/h2&gt;
&lt;p&gt;Idempotency keys are not a silver bullet, and it&amp;rsquo;s worth being explicit about their limits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;They don&amp;rsquo;t replace the outbox pattern.&lt;/strong&gt; If your charge handler also needs to publish an &lt;code&gt;OrderPaidEvent&lt;/code&gt;, you still need transactional guarantees for that event to avoid the classic dual-write problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;They require client cooperation.&lt;/strong&gt; If your mobile client generates a &lt;em&gt;new&lt;/em&gt; UUID on every retry instead of reusing the same one, the whole pattern is defeated. Document this clearly in your API contract, and consider generating the key client-side at the moment the user taps &amp;ldquo;Pay,&amp;rdquo; not per HTTP attempt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;They add a write on every request.&lt;/strong&gt; For extremely high-throughput, low-value endpoints, the extra database round-trip might not be worth it. Reserve this pattern for operations where duplication is actually expensive: payments, order creation, and anything that moves money or inventory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expiration is a trade-off.&lt;/strong&gt; Too short, and legitimate slow retries fall outside the window and double-charge anyway. Too long, and you accumulate an ever-growing table of dead records. A background cleanup job that purges expired keys is worth building from day one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Retries are not an edge case in distributed systems; they&amp;rsquo;re the normal operating condition for microservices talking to payment gateways, webhooks, and each other. The idempotency key pattern doesn&amp;rsquo;t try to prevent retries, it makes them harmless by turning &amp;ldquo;did this get charged?&amp;rdquo; into a lookup instead of a guess. Remember that customer from the opening story, stuck wondering if they&amp;rsquo;d been billed twice? With an idempotent API in place, that retry would have silently returned the exact same success response, and they&amp;rsquo;d never have known there was a network hiccup at all. If you&amp;rsquo;re building a payment API, a webhook receiver, or anything else where duplicate execution has a real cost, treat idempotency keys as a first-class requirement, not an afterthought you bolt on after the first double-charge incident.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thanks for reading! 🎉 To not miss my research in software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Events vs Commands in Software Architecture: A Complete .NET Guide</title><link>https://berkselvi.dev/posts/events-vs-commands-in-software-architecture-a-complete-dotnet-guide/</link><pubDate>Mon, 22 Jun 2026 14:55:00 +0300</pubDate><guid>https://berkselvi.dev/posts/events-vs-commands-in-software-architecture-a-complete-dotnet-guide/</guid><description>&lt;h1 id="demystifying-events-vs-commands-in-software-architecture-a-complete-net-guide"&gt;Demystifying Events vs Commands in Software Architecture: A Complete .NET Guide&lt;/h1&gt;
&lt;p&gt;In the modern landscape of software engineering, shifting from monolithic systems to distributed microservices is a common evolutionary path. As systems grow in complexity, the way different components, modules, and services communicate becomes the critical factor in defining the system&amp;rsquo;s resilience, scalability, and maintainability. If you are building robust, cloud-native applications, you inevitably encounter the need to decouple your components.&lt;/p&gt;</description><content>&lt;h1 id="demystifying-events-vs-commands-in-software-architecture-a-complete-net-guide"&gt;Demystifying Events vs Commands in Software Architecture: A Complete .NET Guide&lt;/h1&gt;
&lt;p&gt;In the modern landscape of software engineering, shifting from monolithic systems to distributed microservices is a common evolutionary path. As systems grow in complexity, the way different components, modules, and services communicate becomes the critical factor in defining the system&amp;rsquo;s resilience, scalability, and maintainability. If you are building robust, cloud-native applications, you inevitably encounter the need to decouple your components.&lt;/p&gt;
&lt;p&gt;At the heart of this decoupling and communication strategy lie two fundamental messaging patterns: &lt;strong&gt;Commands&lt;/strong&gt; and &lt;strong&gt;Events&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;While they might seem similar at first glance—both are essentially messages containing data sent from one part of a system to another—their intent, handling, and architectural implications are vastly different. Misunderstanding the distinction between &lt;strong&gt;events vs commands in software architecture&lt;/strong&gt; is one of the most common pitfalls when designing an &lt;strong&gt;Event-Driven Architecture (EDA)&lt;/strong&gt; or implementing &lt;strong&gt;Command Query Responsibility Segregation (CQRS)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this comprehensive guide, we will explore the theoretical differences between events and commands, analyze when to use which, and walk through a practical storytelling implementation using &lt;strong&gt;.NET&lt;/strong&gt; and C#.&lt;/p&gt;
&lt;h2 id="the-core-communication-problem-in-distributed-systems"&gt;The Core Communication Problem in Distributed Systems&lt;/h2&gt;
&lt;p&gt;Before we dive into the definitions, let&amp;rsquo;s understand the problem we are trying to solve.&lt;/p&gt;
&lt;p&gt;Imagine an e-commerce application. A user clicks the &amp;ldquo;Checkout&amp;rdquo; button. In a traditional, tightly coupled monolithic architecture, the system might sequentially execute the following steps within a single synchronous flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate the cart.&lt;/li&gt;
&lt;li&gt;Deduct inventory.&lt;/li&gt;
&lt;li&gt;Process the payment.&lt;/li&gt;
&lt;li&gt;Generate an invoice.&lt;/li&gt;
&lt;li&gt;Send a confirmation email.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the email server is down, the entire checkout process might fail, rolling back the payment and frustrating the user. This tight coupling creates brittle systems.&lt;/p&gt;
&lt;p&gt;To solve this, architects introduce asynchronous messaging. Instead of calling methods directly, services send messages to each other through a mediator or a message broker (like RabbitMQ, Apache Kafka, or cloud-native solutions on AWS and Google Cloud). But how should these messages be structured? Should the checkout service tell the email service to &lt;em&gt;send an email&lt;/em&gt;, or should it simply state that an &lt;em&gt;order was placed&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;This is where the distinction between Commands and Events becomes crucial.&lt;/p&gt;
&lt;h2 id="what-is-a-command"&gt;What is a Command?&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;Command&lt;/strong&gt; is an expression of &lt;em&gt;intent&lt;/em&gt;. It is a request for the system to perform a specific action or change its state.&lt;/p&gt;
&lt;p&gt;Think of a command as an imperative instruction. You are telling the system exactly what you want it to do. Because it is a request, a command can be rejected. The system might validate the command and decide that it cannot be executed due to business rules, permissions, or invalid data.&lt;/p&gt;
&lt;h3 id="key-characteristics-of-a-command"&gt;Key Characteristics of a Command:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Imperative Naming:&lt;/strong&gt; Commands are named with verbs in the imperative mood (e.g., &lt;code&gt;PlaceOrderCommand&lt;/code&gt;, &lt;code&gt;CreateUserCommand&lt;/code&gt;, &lt;code&gt;ProcessPaymentCommand&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Targeted:&lt;/strong&gt; A command is routed to exactly &lt;strong&gt;one&lt;/strong&gt; handler. You do not broadcast a command to the entire system; you send it to the specific component responsible for that action.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can Fail:&lt;/strong&gt; Because it is a request to change state, the receiving handler can (and often will) reject it if validation fails, returning an error or an exception to the sender.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High Coupling (Behavioral):&lt;/strong&gt; The sender of the command needs to know what action needs to happen, implying a tighter behavioral coupling than events, though the physical coupling might still be abstracted via a message bus.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-is-an-event"&gt;What is an Event?&lt;/h2&gt;
&lt;p&gt;An &lt;strong&gt;Event&lt;/strong&gt;, on the other hand, is a statement of &lt;em&gt;fact&lt;/em&gt;. It is an announcement that something has already happened in the past.&lt;/p&gt;
&lt;p&gt;You cannot reject an event because you cannot change the past. If an event is published, the state change has already occurred. Components in the system listen to events to react and trigger their own subsequent processes.&lt;/p&gt;
&lt;h3 id="key-characteristics-of-an-event"&gt;Key Characteristics of an Event:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Past-Tense Naming:&lt;/strong&gt; Events are named using verbs in the past tense (e.g., &lt;code&gt;OrderPlacedEvent&lt;/code&gt;, &lt;code&gt;UserCreatedEvent&lt;/code&gt;, &lt;code&gt;PaymentProcessedEvent&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broadcasted (Publish/Subscribe):&lt;/strong&gt; An event is published to an event bus or message broker. It can have &lt;strong&gt;zero, one, or multiple&lt;/strong&gt; handlers. The publisher does not know or care who is listening.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cannot Be Rejected:&lt;/strong&gt; The fact has occurred. Subscribers must process it. If a subscriber fails to process an event, it must handle its own retry logic; it cannot tell the publisher to &amp;ldquo;undo&amp;rdquo; the fact.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loose Coupling:&lt;/strong&gt; The sender (publisher) is entirely decoupled from the receivers (subscribers). The publisher simply announces a state change, making it incredibly easy to add new functionality without touching existing code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="key-differences-events-vs-commands"&gt;Key Differences: Events vs Commands&lt;/h2&gt;
&lt;p&gt;To solidify the concepts, let&amp;rsquo;s look at a side-by-side comparison.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Definition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A request to perform an action.&lt;/td&gt;
&lt;td&gt;A notification that something happened.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Naming Convention&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Imperative (e.g., &lt;code&gt;ShipOrder&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Past Tense (e.g., &lt;code&gt;OrderShipped&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing / Handlers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exactly one handler (1:1).&lt;/td&gt;
&lt;td&gt;Zero to many handlers (1:N).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Point-to-point. Sender targets a receiver.&lt;/td&gt;
&lt;td&gt;Publish-Subscribe. Sender broadcasts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can be rejected (validation error).&lt;/td&gt;
&lt;td&gt;Cannot be rejected. Past facts are immutable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sender&amp;rsquo;s Knowledge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Knows &lt;em&gt;what&lt;/em&gt; needs to happen.&lt;/td&gt;
&lt;td&gt;Does not know &lt;em&gt;who&lt;/em&gt; cares about the event.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="the-net-implementation-story-bringing-it-to-life"&gt;The .NET Implementation Story: Bringing it to Life&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s tell a story using .NET to illustrate how these concepts flow together in a real-world application. We will use the highly popular &lt;strong&gt;MediatR&lt;/strong&gt; library, which is the standard implementation for in-memory messaging and CQRS patterns in the .NET ecosystem.&lt;/p&gt;
&lt;p&gt;Imagine we are building an Order Management microservice.&lt;/p&gt;
&lt;h3 id="step-1-defining-the-command"&gt;Step 1: Defining the Command&lt;/h3&gt;
&lt;p&gt;The journey begins when a user submits their cart. The API controller receives the HTTP request and creates a command. Notice the imperative naming. We use C# records because messages should be immutable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Domain.Commands;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// The Command: A request that returns a strongly typed result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PlaceOrderCommand&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Guid CustomerId,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List&amp;lt;CartItem&amp;gt; Items,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ShippingAddress
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) : IRequest&amp;lt;OrderResult&amp;gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CartItem&lt;/span&gt;(Guid ProductId, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; Quantity, &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; Price);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderResult&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; IsSuccess, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Message, Guid? OrderId = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="step-2-handling-the-command"&gt;Step 2: Handling the Command&lt;/h3&gt;
&lt;p&gt;The command is routed to exactly &lt;em&gt;one&lt;/em&gt; handler. This handler contains the core business logic. It validates the request, modifies the database state, and if successful, generates an Event.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Application.Handlers.Commands;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PlaceOrderCommandHandler&lt;/span&gt; : IRequestHandler&amp;lt;PlaceOrderCommand, OrderResult&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IOrderRepository _repository;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IMediator _mediator;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; PlaceOrderCommandHandler(IOrderRepository repository, IMediator mediator)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _repository = repository;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _mediator = mediator;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;OrderResult&amp;gt; Handle(PlaceOrderCommand request, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. Validation (The command can be rejected!)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (request.Items == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; || !request.Items.Any())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderResult(&lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Cart cannot be empty.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. State Change&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; totalAmount = request.Items.Sum(i =&amp;gt; i.Price * i.Quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order(Guid.NewGuid(), request.CustomerId, totalAmount, request.ShippingAddress);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _repository.SaveAsync(order);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. Announcing the Fact (The Event)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; orderPlacedEvent = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderPlacedEvent(order.Id, request.CustomerId, totalAmount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Notice we are Publishing the event, not Sending a command.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _mediator.Publish(orderPlacedEvent, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderResult(&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Order successfully placed.&amp;#34;&lt;/span&gt;, order.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="step-3-defining-the-event"&gt;Step 3: Defining the Event&lt;/h3&gt;
&lt;p&gt;The state has changed. The order is securely stored in the database. Now, the system simply announces this fact to the rest of the application. Notice the past-tense naming.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Domain.Events;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// The Event: A notification that something happened. Does not return a response.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderPlacedEvent&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Guid OrderId,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Guid CustomerId,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; TotalAmount
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) : INotification;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="step-4-reacting-to-the-event-multiple-handlers"&gt;Step 4: Reacting to the Event (Multiple Handlers)&lt;/h3&gt;
&lt;p&gt;This is where the magic of &lt;strong&gt;Event-Driven Architecture&lt;/strong&gt; shines. Because we published an event, we can have multiple independent handlers react to it without modifying our core &lt;code&gt;PlaceOrderCommandHandler&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say the marketing team wants to send a &amp;ldquo;Thank You&amp;rdquo; email, and the logistics team needs to reserve the inventory. We simply create two separate event handlers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Handler A: The Email Service&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Application.Handlers.Events;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SendOrderConfirmationEmailHandler&lt;/span&gt; : INotificationHandler&amp;lt;OrderPlacedEvent&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IEmailService _emailService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; SendOrderConfirmationEmailHandler(IEmailService emailService)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _emailService = emailService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Handle(OrderPlacedEvent notification, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// React to the fact&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _emailService.SendEmailAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; notification.CustomerId,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;$&amp;#34;Thank you for your order! Your total is ${notification.TotalAmount}.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Handler B: The Inventory Service&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Application.Handlers.Events;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ReserveInventoryHandler&lt;/span&gt; : INotificationHandler&amp;lt;OrderPlacedEvent&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IInventoryService _inventoryService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ReserveInventoryHandler(IInventoryService inventoryService)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _inventoryService = inventoryService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Handle(OrderPlacedEvent notification, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// React to the same fact independently&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _inventoryService.ReserveItemsForOrderAsync(notification.OrderId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By structuring our .NET code this way, we have achieved a beautifully loosely coupled system. If tomorrow the analytics team wants to track sales, we don&amp;rsquo;t touch the &lt;code&gt;PlaceOrderCommandHandler&lt;/code&gt;. We just add a &lt;code&gt;TrackSalesAnalyticsHandler&lt;/code&gt; that listens to the &lt;code&gt;OrderPlacedEvent&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="advanced-architectural-considerations"&gt;Advanced Architectural Considerations&lt;/h2&gt;
&lt;p&gt;While the MediatR example perfectly illustrates the in-memory concepts, moving these patterns out of memory and into a distributed cloud environment introduces complex challenges. When utilizing cloud platforms like Google Cloud, AWS, or Azure to run microservices, messaging becomes distributed.&lt;/p&gt;
&lt;p&gt;Here are three advanced patterns you must consider when scaling &lt;strong&gt;Events vs Commands&lt;/strong&gt;:&lt;/p&gt;
&lt;h3 id="1-the-outbox-pattern-guaranteed-delivery"&gt;1. The Outbox Pattern (Guaranteed Delivery)&lt;/h3&gt;
&lt;p&gt;In our C# example, we saved the order to the database and then published the event to MediatR. But what if the database save succeeds, but the application crashes exactly one millisecond before the &lt;code&gt;await _mediator.Publish()&lt;/code&gt; line is executed?&lt;/p&gt;
&lt;p&gt;The state changed, but the event was never fired. The customer&amp;rsquo;s card was charged, but the warehouse never got the memo. This is a catastrophic failure in distributed systems known as the &amp;ldquo;Dual Write Problem.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;To solve this, architects use the &lt;strong&gt;Transactional Outbox Pattern&lt;/strong&gt;. Instead of publishing the event directly, the command handler writes the &lt;code&gt;OrderPlacedEvent&lt;/code&gt; into an &amp;ldquo;Outbox&amp;rdquo; table in the &lt;em&gt;same database transaction&lt;/em&gt; as the order itself. A separate background worker process (like a .NET BackgroundService) constantly reads this Outbox table and publishes the messages to your external broker (e.g., RabbitMQ or Google Cloud Pub/Sub). This guarantees at-least-once delivery of your events.&lt;/p&gt;
&lt;h3 id="2-idempotency-handling-duplicate-events"&gt;2. Idempotency (Handling Duplicate Events)&lt;/h3&gt;
&lt;p&gt;Because distributed messaging systems usually guarantee &amp;ldquo;at-least-once&amp;rdquo; delivery, your event handlers might receive the same &lt;code&gt;OrderPlacedEvent&lt;/code&gt; twice.&lt;/p&gt;
&lt;p&gt;If the &lt;code&gt;SendOrderConfirmationEmailHandler&lt;/code&gt; is not designed carefully, the customer might receive two emails. Worse, if an event triggers a payment capture, they might be charged twice.&lt;/p&gt;
&lt;p&gt;Event handlers must be &lt;strong&gt;idempotent&lt;/strong&gt;. This means that applying the same event multiple times has the same effect as applying it once. In .NET, this is often implemented by maintaining a table of processed &lt;code&gt;EventId&lt;/code&gt;s. Before a handler processes an event, it checks: &lt;em&gt;&amp;ldquo;Have I seen this Event ID before?&amp;rdquo;&lt;/em&gt; If yes, it safely ignores it and returns a success response.&lt;/p&gt;
&lt;h3 id="3-event-sourcing-vs-event-driven-architecture"&gt;3. Event Sourcing vs Event-Driven Architecture&lt;/h3&gt;
&lt;p&gt;It is important not to confuse an Event-Driven Architecture (EDA) with &lt;strong&gt;Event Sourcing&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EDA&lt;/strong&gt; uses events to communicate between services (like our example above). State is still saved in a traditional database table (e.g., an &lt;code&gt;Orders&lt;/code&gt; table with a &lt;code&gt;Status&lt;/code&gt; column).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Sourcing&lt;/strong&gt; is a much more complex paradigm where the events &lt;em&gt;are&lt;/em&gt; the database. Instead of saving the current state of an order, you save an append-only log of events: &lt;code&gt;OrderCreated&lt;/code&gt;, &lt;code&gt;AddressUpdated&lt;/code&gt;, &lt;code&gt;ItemAdded&lt;/code&gt;, &lt;code&gt;OrderCheckedOut&lt;/code&gt;. To figure out the current state, the system replays the events from the beginning of time. While powerful for audit trails, it introduces massive complexity and should be reserved for specific domains that require strict historical accuracy (like financial ledgers).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-which"&gt;When to Use Which?&lt;/h2&gt;
&lt;p&gt;The beauty of software design is choosing the right tool for the job. Here is a quick cheat sheet for when to deploy Commands vs Events in your .NET applications:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use a Command when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need the system to perform a specific action and you know exactly which component should handle it.&lt;/li&gt;
&lt;li&gt;You need to validate user input and potentially reject the request.&lt;/li&gt;
&lt;li&gt;You expect a result back (success, failure, or the ID of a newly created entity).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Use an Event when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A significant state change has occurred, and you want to notify other parts of the system.&lt;/li&gt;
&lt;li&gt;You want to allow multiple, disparate modules to react to a single action without creating tight coupling.&lt;/li&gt;
&lt;li&gt;You are integrating across microservice boundaries and want to ensure services remain autonomous.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Mastering the difference between &lt;strong&gt;events vs commands in software architecture&lt;/strong&gt; is a milestone in a developer&amp;rsquo;s journey toward system design excellence. Commands drive the intent and guard the business rules of your application, ensuring state changes only happen when valid. Events act as the nervous system, broadcasting facts and enabling a highly scalable, loosely coupled ecosystem of microservices.&lt;/p&gt;
&lt;p&gt;By leveraging powerful libraries like MediatR in .NET, and understanding the nuances of asynchronous communication, you can build systems that not only handle the complexities of today&amp;rsquo;s business logic but are also resilient and flexible enough to adapt to the requirements of tomorrow. Remember to name your commands imperatively, treat your events as immutable facts of the past, and embrace the power of decoupling.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thanks for reading! 🎉 To not miss my research in software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>June 2026 AI Economy: LLM Price and Performance Comparison</title><link>https://berkselvi.dev/posts/june-2026-ai-economy-llm-price-and-performance-comparison-copy/</link><pubDate>Fri, 19 Jun 2026 10:38:00 +0300</pubDate><guid>https://berkselvi.dev/posts/june-2026-ai-economy-llm-price-and-performance-comparison-copy/</guid><description>&lt;p&gt;The artificial intelligence (AI) industry is experiencing one of the fastest evolutions in tech history, and as of June 2026, the balance of power in this ecosystem has been completely reshaped. In this comprehensive guide, we will deeply analyze the current state of the market based on the &amp;ldquo;AI Economy in June 2026: LLM Price and Performance Comparison&amp;rdquo; data.&lt;/p&gt;
&lt;p&gt;For companies and developers shaping AI strategies, we have three main focus areas: recognizing the Large Language Model (LLM) providers dominating the market, benchmarking the price/performance of the highest capacity &amp;ldquo;Frontier&amp;rdquo; models, and finally exploring how to protect our wallets—that is, how to optimize costs in this massive data economy.&lt;/p&gt;</description><content>&lt;p&gt;The artificial intelligence (AI) industry is experiencing one of the fastest evolutions in tech history, and as of June 2026, the balance of power in this ecosystem has been completely reshaped. In this comprehensive guide, we will deeply analyze the current state of the market based on the &amp;ldquo;AI Economy in June 2026: LLM Price and Performance Comparison&amp;rdquo; data.&lt;/p&gt;
&lt;p&gt;For companies and developers shaping AI strategies, we have three main focus areas: recognizing the Large Language Model (LLM) providers dominating the market, benchmarking the price/performance of the highest capacity &amp;ldquo;Frontier&amp;rdquo; models, and finally exploring how to protect our wallets—that is, how to optimize costs in this massive data economy.&lt;/p&gt;
&lt;h2 id="western-frontier-leaders-shaping-the-market"&gt;Western &amp;ldquo;Frontier&amp;rdquo; Leaders Shaping the Market&lt;/h2&gt;
&lt;p&gt;The giant companies referred to as &amp;ldquo;Western Frontier Leaders&amp;rdquo; in the industry continue to push the boundaries with massive investments and proprietary systems. The current strategies of these major market players are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OpenAI:&lt;/strong&gt; Sparking the AI revolution, OpenAI continues to lead technological leaps with its GPT-5+ series. Backed by Microsoft&amp;rsquo;s massive investments, the organization proceeds with a capped-profit company structure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Google:&lt;/strong&gt; Shows up in the market with the Gemini series, powered by its proprietary TPU (Tensor Processing Unit) hardware. Google&amp;rsquo;s biggest advantage is its ability to entirely self-fund this massive operation through Alphabet&amp;rsquo;s vast ecosystem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Anthropic:&lt;/strong&gt; Centering the concept of safety, the company differentiates itself from competitors with its &amp;ldquo;Constitutional AI&amp;rdquo; vision. Heavily funded by Amazon and Google, Anthropic is the creator of the famous Claude family, possessing top-tier capabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Microsoft:&lt;/strong&gt; While maintaining its strategic partnership with OpenAI seamlessly, it also takes specific steps for corporate needs. It creates diversity by introducing its internal MAI series to the market, specifically optimized for enterprise reasoning and software tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="global-open-weight-competitors-and-disruptive-innovation"&gt;Global Open-Weight Competitors and Disruptive Innovation&lt;/h2&gt;
&lt;p&gt;Against the high-cost and controlled ecosystems offered by closed systems, the open-source (Open-Weight) world is creating a serious disruptive effect in the market.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DeepSeek:&lt;/strong&gt; Drawing attention with its architectural innovations, the company uses a highly cost-effective &amp;ldquo;Mixture of Experts&amp;rdquo; (MoE) infrastructure. DeepSeek is disrupting Western markets with these state-of-the-art open models.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Alibaba:&lt;/strong&gt; The China-based giant is present in the market with the Qwen series. Self-funded through the Alibaba Cloud infrastructure, the company particularly focuses on multi-agent optimization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Z.AI:&lt;/strong&gt; Born from academic roots as a Tsinghua University spin-off, Z.AI develops the GLM series. The company is the market leader in efficient open-source agent-based models and multi-step reasoning algorithms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;xAI:&lt;/strong&gt; The innovation company led by Elon Musk utilizes the Colossus supercluster, which possesses massive computing power. Furthermore, by processing real-time data flowing from the X platform, it trains dynamic models with a highly natural conversational tone.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="hardware--specialized-hubs"&gt;Hardware &amp;amp; Specialized Hubs&lt;/h3&gt;
&lt;p&gt;Hardware manufacturers, the driving force behind the models, are also directly involved in AI production.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;NVIDIA:&lt;/strong&gt; The undisputed hardware giant doesn&amp;rsquo;t just produce chips. The company is developing its own &amp;ldquo;Nemotron&amp;rdquo; open-weight models, leveraging its internal superclusters and synthetic data compilation pools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Moonshot AI:&lt;/strong&gt; Particularly known as the visionary team behind the popular Kimi chatbot, Moonshot AI focuses on long-context understanding—one of the most challenging areas of AI—and autonomous agentic reasoning at scale.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MiniMax:&lt;/strong&gt; Powering interactive consumer AI applications, the company also produces versatile multimodal models that form the foundation of audio and video platforms like Hailuo.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="detailed-frontier-model-rankings-june-2026-data"&gt;Detailed Frontier Model Rankings: June 2026 Data&lt;/h2&gt;
&lt;p&gt;There are massive gaps between the overall intelligence scores of AI models and the input/output costs they demand per 1 million tokens. As of June 2026, the detailed performance and price table of the market&amp;rsquo;s leading models is as follows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Model Name&lt;/th&gt;
&lt;th&gt;Creator&lt;/th&gt;
&lt;th&gt;Overall Score (0-100)&lt;/th&gt;
&lt;th&gt;Input Cost (1M Tokens)&lt;/th&gt;
&lt;th&gt;Output Cost (1M Tokens)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Mythos 5&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;$10.0&lt;/td&gt;
&lt;td&gt;$50.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GLM-5.2&lt;/td&gt;
&lt;td&gt;Z.AI&lt;/td&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;$1.4&lt;/td&gt;
&lt;td&gt;$4.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemini 3.1 Pro&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;$2.0&lt;/td&gt;
&lt;td&gt;$12.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Qwen3.7 Max&lt;/td&gt;
&lt;td&gt;Alibaba&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;$1.25&lt;/td&gt;
&lt;td&gt;$3.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPT-5.4 Pro&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;$30.0&lt;/td&gt;
&lt;td&gt;$180.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DeepSeek V4 Pro (Max)&lt;/td&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;$1.74&lt;/td&gt;
&lt;td&gt;$3.48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;21&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kimi K2.6&lt;/td&gt;
&lt;td&gt;Moonshot AI&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;$0.95&lt;/td&gt;
&lt;td&gt;$4.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;23&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MiniMax M3&lt;/td&gt;
&lt;td&gt;MiniMax&lt;/td&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;$0.3&lt;/td&gt;
&lt;td&gt;$1.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="efficiency-analysis-and-the-dramatic-cost-gap"&gt;Efficiency Analysis and the Dramatic Cost Gap&lt;/h3&gt;
&lt;p&gt;When we examine the ranking table and efficiency analysis graphs, it is striking that while the performance bars are very close to each other, there are incredible differences in the price bars. When the price bars are scaled according to the massive total cost of OpenAI&amp;rsquo;s flagship GPT-5.4 Pro at $210 ($30 input + $180 output), it is clearly noticeable that the costs of other models remain almost invisibly thin on the graph.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Shift at the Extremes:&lt;/strong&gt; GPT-5.4 Pro offers extraordinary depth and analysis capability with an overall score of 90. However, it demands $180 for 1 million output tokens. In contrast, the 1 million output token cost of the MiniMax M3 model, which has a score of 79, is only around $1.20. The fact that MiniMax M3 offers a 99.3% cheaper option in terms of cost causes a massive paradigm shift in the AI strategies of companies and developers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Realistic Price Gap:&lt;/strong&gt; Alongside extreme examples, there are also significant price advantages in heavily preferred models in the mid-tier that perform closely to one another. The 1 million output cost of the Claude Sonnet 4.6 model, which has a performance score of 82, is $15. Yet, when you use the open-weight DeepSeek V4 Pro model, which has a higher score of 86, your cost drops to $3.48. In other words, simply by choosing the right model, it is possible to gain a direct price advantage of 76.8% by increasing quality rather than compromising it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mid-tier--value-efficiency-market"&gt;Mid-Tier &amp;amp; Value-Efficiency Market&lt;/h3&gt;
&lt;p&gt;The competition in the mid-tier, which is the real driving force of the market, is driving costs even lower.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenAI&amp;rsquo;s &lt;strong&gt;GPT-5.1&lt;/strong&gt; model has a score of 77 but incurs a cost of $10.00 per 1 million outputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DeepSeek V4 Flash&lt;/strong&gt;, despite achieving a score of 74, pushes the limits in the speed-to-cost balance and demands only 28 cents ($0.28) for 1 million output tokens.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Notably, open models like Alibaba&amp;rsquo;s &lt;strong&gt;Qwen3.6-27B&lt;/strong&gt; (72 score) and NVIDIA&amp;rsquo;s &lt;strong&gt;Nemotron 3 Ultra&lt;/strong&gt; (68 score) reach significant ranks on the scoreboards with a $0.00 (zero cost) per token cost, excluding hosting expenses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;xAI&amp;rsquo;s &lt;strong&gt;Grok 4.1 Fast&lt;/strong&gt; model, with a score of 68, has a cost of only $0.50 per 1 million outputs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="economic-impact-choosing-the-right-source-type"&gt;Economic Impact: Choosing the Right Source Type&lt;/h2&gt;
&lt;p&gt;At the end of the day, organizations must make a fundamental choice when determining their AI strategies:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Proprietary Models:&lt;/strong&gt; These models offered by giants like Anthropic or Google provide top-tier engineering support, a closed and secure ecosystem, guaranteed Service Level Agreements (SLA), and strong compliance guardrails. However, in return for all these corporate advantages, much higher, premium prices must be paid.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open-Weight Models:&lt;/strong&gt; These models, on the other hand, offer developers extraordinary, extreme cost savings (often with $0 per token hosting), full data privacy control, and full ownership. Furthermore, as clearly seen in the DeepSeek and Qwen examples, the performance of these models has now reached the scores of top-tier frontier models.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="how-will-we-protect-our-wallets-practical-developer-solutions"&gt;How Will We Protect Our Wallets? (Practical Developer Solutions)&lt;/h2&gt;
&lt;p&gt;This dizzying gap between performance and price makes &amp;ldquo;protecting the wallet&amp;rdquo; a necessity in the tech world. For developers, the practical technological answer to this problem lies in moving beyond traditional boundaries, namely GitHub Copilot.&lt;/p&gt;
&lt;p&gt;Modern software development processes require Universal Access to models like GLM, DeepSeek, and Kimi, which are not available in the limited pool of traditional tools like Copilot and are unsupported by closed systems. The revolutionary infrastructures that stand out at this point are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OpenCode Go:&lt;/strong&gt; This is an advanced subscription system that provides open-source model optimization, bypassing all restrictions to make agent-based coding accessible to everyone. It allows users to connect to any model they want, including Claude, GPT, or Gemini from any provider.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OpenRouter Routing:&lt;/strong&gt; Eliminating the risks and costs of being tied to a single provider, this unified API is a dynamic routing system that instantly switches to a backup model (fallback) in the event of a failure or crash, and ensures you reach the most cost-effective option at any moment with its price balancing features.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In conclusion, remaining competitive in the 2026 AI economy is not just about choosing the most popular option; it is about strategically establishing flexible APIs and open-source infrastructures that can melt intelligence capacity, data privacy, and price advantage into the same melting pot.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thanks for reading! 🎉 To not miss my research in software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Refactoring from an Anemic to a Rich Domain Model in .NET</title><link>https://berkselvi.dev/posts/refactoring-from-an-anemic-to-a-rich-domain-model-in-dotnet/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/refactoring-from-an-anemic-to-a-rich-domain-model-in-dotnet/</guid><description>&lt;p&gt;If you have spent any significant amount of time building enterprise applications in .NET, you have likely encountered the concept of Domain-Driven Design (DDD). DDD promises a way to tackle software complexity by aligning your code with the real-world business domain it represents. However, one of the most common pitfalls developers fall into when adopting DDD—or even just basic Object-Oriented Programming (OOP)—is the creation of an &lt;strong&gt;Anemic Domain Model&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In many .NET architectures, you will see a familiar pattern: &amp;ldquo;entities&amp;rdquo; that are nothing more than bags of public getters and setters, accompanied by massive &amp;ldquo;service&amp;rdquo; classes that contain all the business rules, validation, and state manipulation logic. While this structure might feel comfortable, especially for those coming from a database-centric background, it actively works against the principles of object-oriented design and encapsulation.&lt;/p&gt;</description><content>&lt;p&gt;If you have spent any significant amount of time building enterprise applications in .NET, you have likely encountered the concept of Domain-Driven Design (DDD). DDD promises a way to tackle software complexity by aligning your code with the real-world business domain it represents. However, one of the most common pitfalls developers fall into when adopting DDD—or even just basic Object-Oriented Programming (OOP)—is the creation of an &lt;strong&gt;Anemic Domain Model&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In many .NET architectures, you will see a familiar pattern: &amp;ldquo;entities&amp;rdquo; that are nothing more than bags of public getters and setters, accompanied by massive &amp;ldquo;service&amp;rdquo; classes that contain all the business rules, validation, and state manipulation logic. While this structure might feel comfortable, especially for those coming from a database-centric background, it actively works against the principles of object-oriented design and encapsulation.&lt;/p&gt;
&lt;p&gt;In this comprehensive guide, we will explore exactly what an Anemic Domain Model is, why industry experts consider it an anti-pattern, and how you can refactor your C# applications toward a &lt;strong&gt;Rich Domain Model&lt;/strong&gt;. We will look at practical code examples, discuss how to handle persistence with Entity Framework Core (EF Core), and see how moving logic into your entities makes unit testing a breeze.&lt;/p&gt;
&lt;h2 id="1-the-trap-of-the-anemic-domain-model"&gt;1. The Trap of the Anemic Domain Model&lt;/h2&gt;
&lt;h3 id="what-is-an-anemic-domain-model"&gt;What is an Anemic Domain Model?&lt;/h3&gt;
&lt;p&gt;The term &amp;ldquo;Anemic Domain Model&amp;rdquo; was famously coined by Martin Fowler back in 2003. He described it as an anti-pattern where objects look like real domain entities (often named after nouns in the business space, like &lt;code&gt;Customer&lt;/code&gt;, &lt;code&gt;Order&lt;/code&gt;, or &lt;code&gt;Invoice&lt;/code&gt;) but possess virtually no behavior. Instead, they are simply data containers—C# classes filled with public properties.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at a classic example of an anemic entity in C#:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid CustomerId { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; DateTime OrderDate { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; OrderStatus Status { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; TotalAmount { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; List&amp;lt;OrderItem&amp;gt; Items { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;OrderItem&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ShippingAddress { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; OrderStatus
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Pending,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Paid,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Shipped,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cancelled
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At first glance, this might look completely normal. This is how the vast majority of Entity Framework tutorials teach you to build classes. However, this class is entirely devoid of behavior. It is a &amp;ldquo;dumb&amp;rdquo; object.&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;Order&lt;/code&gt; cannot protect its own state, any part of the application can modify it directly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Wait, an order with no customer, no items, and it&amp;#39;s already marked as shipped?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;order.Status = OrderStatus.Shipped;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;order.TotalAmount = -&lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;m; &lt;span style="color:#75715e"&gt;// Negative amount? Sure, why not!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-service-layer-bloat"&gt;The &amp;ldquo;Service&amp;rdquo; Layer Bloat&lt;/h3&gt;
&lt;p&gt;Because the entity has no logic, the business rules must live somewhere else. This leads to the creation of &amp;ldquo;God classes&amp;rdquo; or massive service layers, often referred to as Transaction Scripts.&lt;/p&gt;
&lt;p&gt;Here is what an &lt;code&gt;OrderService&lt;/code&gt; looks like when dealing with an anemic model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItemToOrder(Guid orderId, OrderItem item)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = _repository.Get(orderId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (order == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Exception(&lt;span style="color:#e6db74"&gt;&amp;#34;Order not found&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (order.Status == OrderStatus.Shipped || order.Status == OrderStatus.Cancelled)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Cannot modify a shipped or cancelled order.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.Items.Add(item);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Recalculate total&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.TotalAmount = order.Items.Sum(i =&amp;gt; i.Price * i.Quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _repository.Save(order);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While this code works, it represents procedural programming disguised as object-oriented programming. The logic concerning what makes an &lt;code&gt;Order&lt;/code&gt; valid is pulled out of the &lt;code&gt;Order&lt;/code&gt; and dumped into the &lt;code&gt;OrderService&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="the-costs-of-anemia"&gt;The Costs of Anemia&lt;/h3&gt;
&lt;p&gt;When your domain model is anemic, you encounter several significant architectural costs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Lack of Encapsulation:&lt;/strong&gt; Any code anywhere can change the state of an entity. It is impossible to guarantee that an &lt;code&gt;Order&lt;/code&gt; is in a valid state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duplicated Logic:&lt;/strong&gt; If you need to recalculate the total amount of an order in a different use case (e.g., applying a discount), you will likely duplicate the recalculation logic in another service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low Cohesion:&lt;/strong&gt; The data (&lt;code&gt;Order&lt;/code&gt;) and the behavior that operates on that data (&lt;code&gt;OrderService&lt;/code&gt;) are separated. You have to navigate through multiple layers to understand the lifecycle of an order.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fragility:&lt;/strong&gt; As the application grows, complex business logic becomes scattered across dozens of services, making it incredibly difficult to track down bugs or refactor the system.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-the-cure-introducing-the-rich-domain-model"&gt;2. The Cure: Introducing the Rich Domain Model&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;Rich Domain Model&lt;/strong&gt; flips this paradigm on its head. In a rich model, entities are intelligent objects. They encapsulate both their data (state) and the business rules (behavior) that govern how that data can be mutated.&lt;/p&gt;
&lt;p&gt;A rich domain model is built on three core pillars:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Always-Valid State (Invariants):&lt;/strong&gt; An entity should never exist in an invalid state. Its creation and mutation are strictly controlled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encapsulation:&lt;/strong&gt; Properties cannot be arbitrarily changed from the outside. State mutations happen exclusively through clearly named methods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavior-Driven:&lt;/strong&gt; Instead of asking an object for its data and making decisions on its behalf, you tell the object what to do (the &lt;em&gt;Tell, Don&amp;rsquo;t Ask&lt;/em&gt; principle).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s begin the process of refactoring our anemic &lt;code&gt;Order&lt;/code&gt; entity into a behavior-driven, rich domain entity in C#.&lt;/p&gt;
&lt;h2 id="3-step-by-step-refactoring-to-a-rich-domain-model-in-c"&gt;3. Step-by-Step Refactoring to a Rich Domain Model in C#&lt;/h2&gt;
&lt;h3 id="step-1-lock-down-the-state-private-setters"&gt;Step 1: Lock Down the State (Private Setters)&lt;/h3&gt;
&lt;p&gt;The very first step to curing an anemic model is to stop the bleeding. We must prevent external code from arbitrarily changing properties. We achieve this by making all property setters &lt;code&gt;private&lt;/code&gt; or &lt;code&gt;protected&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid CustomerId { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; DateTime OrderDate { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; OrderStatus Status { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; TotalAmount { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ShippingAddress { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// We don&amp;#39;t want external code adding to the list directly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; List&amp;lt;OrderItem&amp;gt; _items = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;OrderItem&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IReadOnlyCollection&amp;lt;OrderItem&amp;gt; Items =&amp;gt; _items.AsReadOnly();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice what happened here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;set&lt;/code&gt; accessors are now &lt;code&gt;private&lt;/code&gt;. You can read the state of the order, but you cannot change it directly.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Items&lt;/code&gt; collection is now exposed as an &lt;code&gt;IReadOnlyCollection&amp;lt;OrderItem&amp;gt;&lt;/code&gt;. The actual list is hidden behind a &lt;code&gt;private readonly&lt;/code&gt; backing field. This prevents someone from calling &lt;code&gt;order.Items.Add(...)&lt;/code&gt; from the outside.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="step-2-meaningful-constructors"&gt;Step 2: Meaningful Constructors&lt;/h3&gt;
&lt;p&gt;If setters are private, how do we create an object? We use a parameterized constructor that requires all the necessary information to create a valid entity from the very start.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... properties ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Order(Guid id, Guid customerId, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; shippingAddress)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (id == Guid.Empty) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentException(&lt;span style="color:#e6db74"&gt;&amp;#34;Order ID cannot be empty.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (customerId == Guid.Empty) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentException(&lt;span style="color:#e6db74"&gt;&amp;#34;Customer ID cannot be empty.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(shippingAddress)) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentException(&lt;span style="color:#e6db74"&gt;&amp;#34;Shipping address is required.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CustomerId = customerId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ShippingAddress = shippingAddress;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Initial defaults&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; OrderDate = DateTime.UtcNow;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = OrderStatus.Pending;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TotalAmount = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;m;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, it is literally impossible to create an &lt;code&gt;Order&lt;/code&gt; without a Customer ID or a Shipping Address. The compiler enforces your business rules.&lt;/p&gt;
&lt;h3 id="step-3-introduce-domain-methods-behavior"&gt;Step 3: Introduce Domain Methods (Behavior)&lt;/h3&gt;
&lt;p&gt;Now that the state is protected, we need to introduce explicit, intention-revealing methods to allow state mutations. This is where we move the logic out of the &lt;code&gt;OrderService&lt;/code&gt; and into the &lt;code&gt;Order&lt;/code&gt; entity.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s implement the logic for adding an item and marking the order as shipped.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... constructor and properties ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItem(Product product, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; quantity)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (Status != OrderStatus.Pending)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Can only add items to a pending order.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (quantity &amp;lt;= &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentException(&lt;span style="color:#e6db74"&gt;&amp;#34;Quantity must be greater than zero.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; existingItem = _items.SingleOrDefault(i =&amp;gt; i.ProductId == product.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (existingItem != &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; existingItem.IncreaseQuantity(quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _items.Add(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderItem(product.Id, product.Price, quantity));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecalculateTotal();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Ship()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (Status != OrderStatus.Paid)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Cannot ship an order that has not been paid.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!_items.Any())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Cannot ship an empty order.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = OrderStatus.Shipped;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; RecalculateTotal()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TotalAmount = _items.Sum(item =&amp;gt; item.Price * item.Quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-resulting-application-layer"&gt;The Resulting Application Layer&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s look at what our &lt;code&gt;OrderService&lt;/code&gt; (now often called a Command Handler or Application Service in Clean Architecture) looks like after this refactoring:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItemToOrder(Guid orderId, Product product, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; quantity)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = _repository.Get(orderId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (order == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Exception(&lt;span style="color:#e6db74"&gt;&amp;#34;Order not found&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// The logic is no longer here! We simply delegate to the domain.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.AddItem(product, quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _repository.Save(order);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Application Service is now thin and acts purely as an orchestrator. It fetches the entity from the database, invokes the behavior on the entity, and saves the new state back to the database. All the heavy lifting, business rules, and invariants are neatly encapsulated inside the &lt;code&gt;Order&lt;/code&gt; class. This perfectly aligns with Domain-Driven Design principles.&lt;/p&gt;
&lt;h2 id="4-entity-framework-core-mapping-rich-models-to-the-database"&gt;4. Entity Framework Core: Mapping Rich Models to the Database&lt;/h2&gt;
&lt;p&gt;A major historical reason developers stuck with Anemic Domain Models was because Object-Relational Mappers (ORMs) required them. Early versions of Entity Framework demanded public setters, public parameterless constructors, and public collections to map tables to objects.&lt;/p&gt;
&lt;p&gt;Thankfully, modern &lt;strong&gt;Entity Framework Core (EF Core)&lt;/strong&gt; is incredibly powerful and actively supports Domain-Driven Design patterns. You can maintain a completely encapsulated Rich Domain Model without leaking persistence concerns into your domain.&lt;/p&gt;
&lt;p&gt;Here are the key EF Core features you need to persist Rich Domain Models:&lt;/p&gt;
&lt;h3 id="1-parameterless-constructors-for-ef-core"&gt;1. Parameterless Constructors for EF Core&lt;/h3&gt;
&lt;p&gt;When EF Core reads data from the database, it needs a way to instantiate your objects before setting their properties. You don&amp;rsquo;t want a public parameterless constructor because that breaks encapsulation. Instead, you can provide a &lt;strong&gt;private&lt;/strong&gt; parameterless constructor specifically for EF Core.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Public constructor for your application code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Order(Guid id, Guid customerId, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; shippingAddress) { ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Private constructor for EF Core&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; Order() { }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;EF Core uses reflection to instantiate the object, so a private constructor works perfectly while keeping your API safe from other developers.&lt;/p&gt;
&lt;h3 id="2-mapping-private-backing-fields"&gt;2. Mapping Private Backing Fields&lt;/h3&gt;
&lt;p&gt;EF Core is smart enough to find and bind to private backing fields automatically. If you have an &lt;code&gt;IReadOnlyCollection&amp;lt;OrderItem&amp;gt; Items&lt;/code&gt; property and a &lt;code&gt;_items&lt;/code&gt; backing field, EF Core will map the relationships directly to the &lt;code&gt;_items&lt;/code&gt; field when materializing data from the database.&lt;/p&gt;
&lt;p&gt;If your naming convention differs, you can configure it explicitly in the &lt;code&gt;OnModelCreating&lt;/code&gt; method or your &lt;code&gt;IEntityTypeConfiguration&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderConfiguration&lt;/span&gt; : IEntityTypeConfiguration&amp;lt;Order&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Configure(EntityTypeBuilder&amp;lt;Order&amp;gt; builder)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.HasKey(o =&amp;gt; o.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Tell EF Core to use the private backing field for the collection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.Metadata.FindNavigation(nameof(Order.Items))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .SetPropertyAccessMode(PropertyAccessMode.Field);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-value-objects-via-owned-entities"&gt;3. Value Objects via Owned Entities&lt;/h3&gt;
&lt;p&gt;In a Rich Domain Model, you often want to group related properties into &lt;strong&gt;Value Objects&lt;/strong&gt;. For example, instead of storing &lt;code&gt;Currency&lt;/code&gt; and &lt;code&gt;Amount&lt;/code&gt; separately on an entity, you can create a &lt;code&gt;Money&lt;/code&gt; value object.&lt;/p&gt;
&lt;p&gt;EF Core supports this through &lt;strong&gt;Owned Types&lt;/strong&gt; (&lt;code&gt;OwnsOne&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// The Value Object&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Money&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Currency { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; Amount { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Money(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; currency, &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; amount)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Currency = currency;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Amount = amount;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; Money() { } &lt;span style="color:#75715e"&gt;// For EF Core&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Inside your Entity Configuration:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.OwnsOne(o =&amp;gt; o.Price, priceBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; priceBuilder.Property(p =&amp;gt; p.Currency).HasColumnName(&lt;span style="color:#e6db74"&gt;&amp;#34;PriceCurrency&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; priceBuilder.Property(p =&amp;gt; p.Amount).HasColumnName(&lt;span style="color:#e6db74"&gt;&amp;#34;PriceAmount&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This allows you to maintain a highly expressive, object-oriented domain model while neatly flattening the data into a single SQL table.&lt;/p&gt;
&lt;h2 id="5-the-magic-of-unit-testing-rich-domain-models"&gt;5. The Magic of Unit Testing Rich Domain Models&lt;/h2&gt;
&lt;p&gt;One of the most profound benefits of moving logic into your entities is how much easier your unit testing becomes.&lt;/p&gt;
&lt;p&gt;When your logic lives in a massive &lt;code&gt;OrderService&lt;/code&gt;, writing a unit test is painful. You have to mock the &lt;code&gt;IOrderRepository&lt;/code&gt;, mock the &lt;code&gt;IProductRepository&lt;/code&gt;, mock the logging service, arrange the database state, and finally test the logic. The test setup is often larger than the test itself.&lt;/p&gt;
&lt;p&gt;But when you use a Rich Domain Model, the business logic relies purely on the state of the entity. There are no external dependencies, no databases, and no APIs inside your &lt;code&gt;Order&lt;/code&gt; class. It is pure C# code.&lt;/p&gt;
&lt;p&gt;Testing the domain logic becomes incredibly straightforward and lightning-fast:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderTests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Fact]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItem_Should_ThrowException_When_OrderIsNotPending()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Arrange&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order(Guid.NewGuid(), Guid.NewGuid(), &lt;span style="color:#e6db74"&gt;&amp;#34;123 Main St&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Use reflection or a test-specific setup to transition state to Shipped &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// (Assuming we have a Pay() then Ship() method)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.Pay();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.Ship();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; product = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Product(Guid.NewGuid(), &lt;span style="color:#e6db74"&gt;&amp;#34;Laptop&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;m);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Act &amp;amp; Assert&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exception = Assert.Throws&amp;lt;InvalidOperationException&amp;gt;(() =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.AddItem(product, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.Equal(&lt;span style="color:#e6db74"&gt;&amp;#34;Can only add items to a pending order.&amp;#34;&lt;/span&gt;, exception.Message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Fact]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItem_Should_IncreaseTotalAmount()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Arrange&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order(Guid.NewGuid(), Guid.NewGuid(), &lt;span style="color:#e6db74"&gt;&amp;#34;123 Main St&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; product = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Product(Guid.NewGuid(), &lt;span style="color:#e6db74"&gt;&amp;#34;Laptop&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;m);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Act&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.AddItem(product, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Assert&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.Equal(&lt;span style="color:#ae81ff"&gt;2000&lt;/span&gt;m, order.TotalAmount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.Single(order.Items);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice how there are &lt;strong&gt;zero mocks&lt;/strong&gt; in this test? Because a Rich Domain Model isolates the business logic from infrastructure concerns, your domain layer achieves high testability. You can cover thousands of complex business permutations in milliseconds because you are only executing in-memory C# classes.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Refactoring from an Anemic Domain Model to a Rich Domain Model requires a shift in mindset. You must move away from viewing your entities as mere reflections of database tables (data structures) and start viewing them as living business concepts with explicit behavior.&lt;/p&gt;
&lt;p&gt;By applying principles like private setters, meaningful constructors, explicit domain methods, and adhering to the &lt;em&gt;Tell, Don&amp;rsquo;t Ask&lt;/em&gt; rule, you can centralize your business logic exactly where it belongs: inside the entity.&lt;/p&gt;
&lt;p&gt;While transitioning requires a learning curve, the integration of EF Core&amp;rsquo;s advanced mapping capabilities—like private backing fields and owned entities—means you no longer have to compromise your object-oriented design for the sake of your database. The long-term rewards are undeniable: a highly cohesive, deeply encapsulated, and easily testable codebase that actively resists bugs and technical debt.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Legacy WCF/SOAP to Modern .NET: Migration Without Compromise</title><link>https://berkselvi.dev/posts/legacy-wcf-soap-to-modern-dotnet-migration/</link><pubDate>Fri, 12 Dec 2025 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/legacy-wcf-soap-to-modern-dotnet-migration/</guid><description>&lt;h1 id="legacy-wcfsoap-to-modern-net-migration-without-compromise"&gt;Legacy WCF/SOAP to Modern .NET: Migration Without Compromise&lt;/h1&gt;
&lt;p&gt;You’ve just scoped out the perfect migration plan. The roadmap looks beautiful: you are going to take that aging, monolith .NET Framework 4.8 WCF application and strangle it. You’ll extract the business logic, rewrite the interface as a sleek, stateless REST API on .NET 10, and finally shut down the old IIS server.&lt;/p&gt;
&lt;p&gt;You present the plan to the stakeholders. The internal teams are cheering. The DevOps guys are already writing the Dockerfiles. Everyone is very excited.&lt;/p&gt;</description><content>&lt;h1 id="legacy-wcfsoap-to-modern-net-migration-without-compromise"&gt;Legacy WCF/SOAP to Modern .NET: Migration Without Compromise&lt;/h1&gt;
&lt;p&gt;You’ve just scoped out the perfect migration plan. The roadmap looks beautiful: you are going to take that aging, monolith .NET Framework 4.8 WCF application and strangle it. You’ll extract the business logic, rewrite the interface as a sleek, stateless REST API on .NET 10, and finally shut down the old IIS server.&lt;/p&gt;
&lt;p&gt;You present the plan to the stakeholders. The internal teams are cheering. The DevOps guys are already writing the Dockerfiles. Everyone is very excited.&lt;/p&gt;
&lt;p&gt;And then, the email arrives.&lt;/p&gt;
&lt;p&gt;It’s from the &amp;ldquo;Legacy Integrations&amp;rdquo; team at a major partner, or perhaps a vendor managing a hardware device that hasn&amp;rsquo;t had a firmware update since 2014. Their message is polite but firm:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;We cannot consume a REST API. Our systems require a SOAP envelope. We need the WSDL to remain exactly as it is. If you change the contract, we break.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Suddenly, your migration plan hits a brick wall. You are faced with two bad choices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Abort the migration:&lt;/strong&gt; Keep the entire project on .NET Framework just to satisfy one interface.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &amp;ldquo;Sidecar&amp;rdquo; Nightmare:&lt;/strong&gt; Build the new system in .NET Core, but keep a zombie .NET Framework server running just to proxy SOAP requests to the new API.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But there is a third option. A bridge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CoreWCF.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CoreWCF allows us to keep the external contract (SOAP) identical to the legacy system, while completely rewriting the hosting model, dependency injection, and performance characteristics using modern .NET.&lt;/p&gt;
&lt;p&gt;In this post, we are going to migrate a &lt;code&gt;InvoiceService&lt;/code&gt; step-by-step. We will start with a raw endpoint, secure it with modern authorization, and finally implement sophisticated error handling that bridges the gap between RESTful &lt;code&gt;ProblemDetails&lt;/code&gt; and SOAP Faults.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-1-the-hello-world-of-migration"&gt;Step 1: The &amp;ldquo;Hello World&amp;rdquo; of Migration&lt;/h2&gt;
&lt;p&gt;Our first goal is simply to get the lights on. We want to prove that we can serve the &lt;code&gt;IInvoiceService&lt;/code&gt; contract using the Kestrel web server instead of IIS.&lt;/p&gt;
&lt;p&gt;We aren&amp;rsquo;t worrying about security or error handling yet. We just want to see XML flowing.&lt;/p&gt;
&lt;h3 id="the-contract-and-service"&gt;The Contract and Service&lt;/h3&gt;
&lt;p&gt;We keep our Service Contract almost exactly as it was in the old system. The only difference is that we are embracing &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; for true asynchronous processing—a massive performance win over the old WCF implementation. Additionally, we have slightly renamed the existing request and response models to better adhere to the Mediator pattern.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Sample.API.WCF/IInvoiceService.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.Business.Features.Invoices.CreateInvoice;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ServiceContract]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IInvoiceService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [OperationContract]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task&amp;lt;CreateInvoiceCommandResponse&amp;gt; CreateInvoice(CreateInvoiceCommand command);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Notice: No attributes here yet, just pure logic.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InvoiceService&lt;/span&gt;(ISender mediator) : IInvoiceService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;CreateInvoiceCommandResponse&amp;gt; CreateInvoice(CreateInvoiceCommand command)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; mediator.Send(command);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-configuration-step-1"&gt;The Configuration (Step 1)&lt;/h3&gt;
&lt;p&gt;Here is the &lt;code&gt;Program.cs&lt;/code&gt; for the most basic setup. We strip away everything unnecessary.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs - Step 1: Basic WCF Hosting&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 1. Add CoreWCF basics to the DI container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;InvoiceService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelServices();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelMetadata();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddSingleton&amp;lt;IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 2. Register our MediatR and Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddMediatR(cfg =&amp;gt; cfg.RegisterServicesFromAssembly(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(Program).Assembly));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 3. Configure the CoreWCF Middleware&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseServiceModel(serviceBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceBuilder.AddService&amp;lt;InvoiceService&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddServiceEndpoint&amp;lt;InvoiceService, IInvoiceService&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BasicHttpBinding(), &lt;span style="color:#e6db74"&gt;&amp;#34;/Services/InvoiceService.svc&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;// The endpoint URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Enable WSDL generation so the stubborn client can update their reference if needed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; serviceMetadataBehavior = app.Services.GetRequiredService&amp;lt;ServiceMetadataBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceMetadataBehavior.HttpGetEnabled = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, we have achieved victory on the transport layer. The integrator can hit &lt;code&gt;/Services/InvoiceService.svc&lt;/code&gt;, send an XML envelope, and our modern MediatR handlers process the logic.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-2-adding-authorization"&gt;Step 2: Adding Authorization&lt;/h2&gt;
&lt;p&gt;The &amp;ldquo;Plain&amp;rdquo; service works, but it&amp;rsquo;s dangerous. In the old WCF days, many times developers did not add any authorization support to endpoints and configuring security meant dealing with security certificates.&lt;/p&gt;
&lt;p&gt;In CoreWCF, &lt;strong&gt;security is just ASP.NET Core security&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If your REST API uses JWT Bearer tokens or Cookie Auth, your CoreWCF endpoints can use them too. We don&amp;rsquo;t need to reinvent the wheel. We simply decorate our service with &lt;code&gt;[Authorize]&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="the-updated-service"&gt;The Updated Service&lt;/h3&gt;
&lt;p&gt;We add &lt;code&gt;[Authorize]&lt;/code&gt; and inject &lt;code&gt;IHttpContextAccessor&lt;/code&gt; to prove we can access the user context.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Authorization;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.Shared.Services.Interfaces;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Authorize]&lt;/span&gt; &lt;span style="color:#75715e"&gt;// &amp;lt;--- The magic keyword. This is standard ASP.NET Core auth.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InvoiceService&lt;/span&gt;(ISender mediator, IHttpContextAccessor httpContextAccessor) : IInvoiceService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;Response&amp;gt; CreateInvoice(CreateInvoiceCommand command)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; context = httpContextAccessor.HttpContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(context?.User.Identity?.Name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; mediator.Send(command);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-configuration-step-2"&gt;The Configuration (Step 2)&lt;/h3&gt;
&lt;p&gt;We need to layer the ASP.NET Core Auth middleware &lt;em&gt;before&lt;/em&gt; the CoreWCF middleware.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs - Step 2: Adding Authorization&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Authentication.JwtBearer; &lt;span style="color:#75715e"&gt;// Assuming JWT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 1. Standard ASP.NET Core Authentication Setup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.MapInboundClaims = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.TokenValidationParameters = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; TokenValidationParameters
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuer = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateAudience = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateLifetime = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuerSigningKey = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidIssuer = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidAudience = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Audience&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IssuerSigningKey = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:SecretKey&amp;#34;&lt;/span&gt;]!))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthorization(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.DefaultPolicy = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .RequireAuthenticatedUser()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;InvoiceService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelServices();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelMetadata();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddSingleton&amp;lt;IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddMediatR(cfg =&amp;gt; cfg.RegisterServicesFromAssembly(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(Program).Assembly));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 2. IMPORTANT: Auth middleware must run before WCF middleware&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseServiceModel(serviceBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceBuilder.AddService&amp;lt;InvoiceService&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddServiceEndpoint&amp;lt;InvoiceService, IInvoiceService&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BasicHttpBinding(), &lt;span style="color:#e6db74"&gt;&amp;#34;/Services/InvoiceService.svc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; serviceMetadataBehavior = app.Services.GetRequiredService&amp;lt;ServiceMetadataBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceMetadataBehavior.HttpGetEnabled = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, if the integrator sends a request without a valid standard HTTP &lt;code&gt;Authorization: Bearer&lt;/code&gt; header, CoreWCF will reject it with a 401 before it even reaches our code. Of course our customer needs to implement additional step before making a request but without authn &amp;amp; authz any app will be prone to attacks.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-3-the-universal-translator-unified-error-handling"&gt;Step 3: The Universal Translator (Unified Error Handling)&lt;/h2&gt;
&lt;p&gt;This is the hardest part of the story.&lt;/p&gt;
&lt;p&gt;In a modern REST API, when something goes wrong (e.g., &amp;ldquo;Invoice not found&amp;rdquo;), we throw a custom &lt;code&gt;BadRequestException&lt;/code&gt; and return a JSON &lt;code&gt;ProblemDetails&lt;/code&gt; (RFC 7807) response.&lt;/p&gt;
&lt;p&gt;In WCF, we are expected to return a &lt;code&gt;FaultException&lt;/code&gt; wrapped in XML.&lt;/p&gt;
&lt;p&gt;If we don&amp;rsquo;t handle this, our nice &lt;code&gt;ValidationException&lt;/code&gt; will crash the service and return a generic, ugly &amp;ldquo;Internal Server Error&amp;rdquo; to the client. We need a translator that catches our modern Exceptions and translates them into SOAP Faults that still contain all the rich data (like validation error fields).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Diagnostics;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Xml.Linq;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Channels;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Dispatcher;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.Domain.Constants;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.Domain.Exceptions;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Mvc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.Extensions.Localization;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Sample.API.Filters;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// CoreWCF Error Handler that catches exceptions and returns ProblemDetails as SOAP Fault.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// This acts as a bridge between modern Exception definitions and legacy SOAP faults.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ExceptionHandlerErrorHandler&lt;/span&gt; : IErrorHandler
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; HttpRfcBadRequest = &lt;span style="color:#e6db74"&gt;&amp;#34;https://tools.ietf.org/html/rfc7231#section-6.5.1&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ProblemDetailsNamespace = &lt;span style="color:#e6db74"&gt;&amp;#34;urn:ietf:rfc:7807&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;ExceptionHandlerErrorHandler&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IHttpContextAccessor _httpContextAccessor;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IStringLocalizer&amp;lt;ExceptionHandleMiddleware&amp;gt; _localizer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; Dictionary&amp;lt;Type, Func&amp;lt;Exception, ProblemDetails&amp;gt;&amp;gt; _exceptionHandlers;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ExceptionHandlerErrorHandler(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ILogger&amp;lt;ExceptionHandlerErrorHandler&amp;gt; logger,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IHttpContextAccessor httpContextAccessor,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IStringLocalizer&amp;lt;ExceptionHandleMiddleware&amp;gt; localizer)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _httpContextAccessor = httpContextAccessor;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _localizer = localizer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Map domain exceptions to specific ProblemDetails handlers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _exceptionHandlers = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(BadRequestException), HandleBadRequestException },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Add as many as you like&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; HandleError(Exception error)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Return true to indicate we handled the error and the session is not faulted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ProvideFault(Exception error, MessageVersion version, &lt;span style="color:#66d9ef"&gt;ref&lt;/span&gt; Message fault)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. Convert Exception to ProblemDetails&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; problemDetails = CreateProblemDetails(error);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. Transform ProblemDetails to XML Element for the SOAP Body&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; problemDetailsElement = TransformToXml(problemDetails);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. Create a FaultException with the problem details as detail&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; faultException = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FaultException&amp;lt;XElement&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; problemDetailsElement,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FaultReason(problemDetails.Title ?? &lt;span style="color:#e6db74"&gt;&amp;#34;An error occurred&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FaultCode(GetFaultCodeFromStatus(problemDetails.Status ?? &lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ProblemDetails&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; messageFault = faultException.CreateMessageFault();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fault = Message.CreateMessage(version, messageFault, faultException.Action);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 4. Set the underlying HTTP status code (important for monitoring/observability)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SetHttpStatusCode(problemDetails.Status ?? StatusCodes.Status500InternalServerError);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; ProblemDetails CreateProblemDetails(Exception exception)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exceptionType = exception.GetType();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (_exceptionHandlers.TryGetValue(exceptionType, &lt;span style="color:#66d9ef"&gt;out&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; handler))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogWarning(&lt;span style="color:#e6db74"&gt;&amp;#34;Handled Exception {ExceptionMessage}: Time {Time}&amp;#34;&lt;/span&gt;, exception.Message, DateTime.UtcNow);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; pd = handler(exception);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EnrichProblemDetails(pd);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; pd;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogError(exception, &lt;span style="color:#e6db74"&gt;&amp;#34;Unhandled Exception: Time {Time}&amp;#34;&lt;/span&gt;, DateTime.UtcNow);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; unknownPd = HandleUnknownException(exception);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EnrichProblemDetails(unknownPd);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; unknownPd;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// --- Specific Exception Handlers ---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; ProblemDetails HandleBadRequestException(Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exception = (BadRequestException)ex;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ProblemDetails
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = StatusCodes.Status400BadRequest,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Type = HttpRfcBadRequest,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = _localizer[Localized.BadRequestExceptionTitle],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Detail = _localizer[exception.LocalizedMessage, exception.Arguments]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; ProblemDetails HandleValidationException(Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exception = (ValidationException)ex;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ValidationProblemDetails(exception.Errors)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = StatusCodes.Status400BadRequest,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Type = HttpRfcBadRequest,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = _localizer[Localized.ValidationExceptionTitle]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; ProblemDetails HandleUnknownException(Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ProblemDetails
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = StatusCodes.Status500InternalServerError,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Type = HttpRfcInternalError,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = &lt;span style="color:#e6db74"&gt;&amp;#34;An unexpected error occurred&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Detail = ex.Message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// --- Helpers ---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; EnrichProblemDetails(ProblemDetails result)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Add distributed tracing IDs to the error so clients can report them back to us&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlation = AsyncStorage&amp;lt;Correlation&amp;gt;.Retrieve();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlation &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result.Extensions.Add(&lt;span style="color:#e6db74"&gt;&amp;#34;correlationId&amp;#34;&lt;/span&gt;, correlation.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; traceId = Activity.Current?.Id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (traceId &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result.Extensions.Add(nameof(traceId), traceId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; XElement TransformToXml(ProblemDetails problemDetails)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; ns = XNamespace.Get(ProblemDetailsNamespace);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; element = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;problem&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;, problemDetails.Type),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;, problemDetails.Title),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;, problemDetails.Status),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;detail&amp;#34;&lt;/span&gt;, problemDetails.Detail ?? &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; extension &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; problemDetails.Extensions)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; element.Add(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + extension.Key, extension.Value?.ToString() ?? &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (problemDetails &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; ValidationProblemDetails validationProblemDetails &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; validationProblemDetails.Errors.Count &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; errorsElement = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;errors&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; error &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; validationProblemDetails.Errors)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; errorElement = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XAttribute(&lt;span style="color:#e6db74"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;, error.Key));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; message &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; error.Value)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; errorElement.Add(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;, message));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; errorsElement.Add(errorElement);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; element.Add(errorsElement);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; element;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetFaultCodeFromStatus(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; status)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; status &lt;span style="color:#66d9ef"&gt;switch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;400&lt;/span&gt; =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;BadRequest&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;401&lt;/span&gt; =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Unauthorized&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;403&lt;/span&gt; =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Forbidden&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;404&lt;/span&gt; =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;NotFound&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _ =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;InternalServerError&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; SetHttpStatusCode(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; statusCode)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; httpContext = _httpContextAccessor.HttpContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (httpContext &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; httpContext.Response.StatusCode = statusCode;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-configuration-step-3"&gt;The Configuration (Step 3)&lt;/h3&gt;
&lt;p&gt;We have to wire up this error handler as an &lt;code&gt;IServiceBehavior&lt;/code&gt;. This ensures it applies to every single call made to the service.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs - Step 3: Production Ready with Error Handling&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Channels;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Dispatcher;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.API.Filters; &lt;span style="color:#75715e"&gt;// Where our ErrorHandler lives&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.Extensions.DependencyInjection.Extensions;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.MapInboundClaims = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.TokenValidationParameters = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; TokenValidationParameters
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuer = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateAudience = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateLifetime = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuerSigningKey = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidIssuer = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidAudience = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Audience&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IssuerSigningKey = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:SecretKey&amp;#34;&lt;/span&gt;]!))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthorization(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.DefaultPolicy = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .RequireAuthenticatedUser()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;InvoiceService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelServices();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelMetadata();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton&amp;lt;IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton&amp;lt;IErrorHandler, ExceptionHandlerErrorHandler&amp;gt;(); &lt;span style="color:#75715e"&gt;// these lines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton&amp;lt;IServiceBehavior, ExceptionHandlerServiceBehaviour&amp;gt;(); &lt;span style="color:#75715e"&gt;// should be added.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseServiceModel(serviceBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceBuilder.AddService&amp;lt;InvoiceService&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddServiceEndpoint&amp;lt;InvoiceService, IInvoiceService&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BasicHttpBinding(), &lt;span style="color:#e6db74"&gt;&amp;#34;/Services/InvoiceService.svc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; serviceMetadataBehavior = app.Services.GetRequiredService&amp;lt;ServiceMetadataBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceMetadataBehavior.HttpGetEnabled = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-happy-ending"&gt;The Happy Ending&lt;/h3&gt;
&lt;p&gt;By Step 3, we have achieved something remarkable:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Integrator&lt;/strong&gt; is happy because they are still sending SOAP requests to &lt;code&gt;/Services/InvoiceService.svc&lt;/code&gt;. They didn&amp;rsquo;t have to change their integration pattern.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Developer&lt;/strong&gt; is happy because the &lt;code&gt;InvoiceService&lt;/code&gt; is clean. It uses &lt;code&gt;MediatR&lt;/code&gt;. It doesn&amp;rsquo;t have &lt;code&gt;try/catch&lt;/code&gt; blocks cluttering the business logic. It relies on &lt;code&gt;[Authorize]&lt;/code&gt; for security.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Operations Team&lt;/strong&gt; is happy because the logs show consistent &lt;code&gt;ProblemDetails&lt;/code&gt; structures regardless of whether the request came from a REST client or a SOAP client.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We didn&amp;rsquo;t just &amp;ldquo;port&amp;rdquo; the code; we modernized the ecosystem around it, leaving the WCF interface as a compatibility layer rather than a shackle.&lt;/p&gt;</content></item><item><title>gRPC in .NET Microservices: Comparison with REST</title><link>https://berkselvi.dev/posts/grpc-in-net-microservices-comparison-with-rest/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/grpc-in-net-microservices-comparison-with-rest/</guid><description>&lt;p&gt;When .NET microservices need to communcate synchronously with each other, two main options usually come up: REST API and gRPC API.&lt;/p&gt;
&lt;p&gt;REST works with JSON over the HTTP/1.1 protocol; it’s easy to read, supported by all browsers, and simple to debug. However, compared to gRPC, REST API payloads are larger, and each request opens its own TCP connection. gRPC, on the other hand, compresses data transmission with Protocol Buffers and carries all traffic over a single HTTP/2 connection, minimizing latency. In this post, we’ll look at how both REST and gRPC work, compare them with examples on .NET 9, and examine which solution is more advantageous in which scenarios. Let’s get started.&lt;/p&gt;</description><content>&lt;p&gt;When .NET microservices need to communcate synchronously with each other, two main options usually come up: REST API and gRPC API.&lt;/p&gt;
&lt;p&gt;REST works with JSON over the HTTP/1.1 protocol; it’s easy to read, supported by all browsers, and simple to debug. However, compared to gRPC, REST API payloads are larger, and each request opens its own TCP connection. gRPC, on the other hand, compresses data transmission with Protocol Buffers and carries all traffic over a single HTTP/2 connection, minimizing latency. In this post, we’ll look at how both REST and gRPC work, compare them with examples on .NET 9, and examine which solution is more advantageous in which scenarios. Let’s get started.&lt;/p&gt;
&lt;h2 id="what-is-this-rpc"&gt;What is this RPC?&lt;/h2&gt;
&lt;p&gt;If you’ve been developing in the .NET ecosystem for a long time, this is probably not your first encounter with the concept of RPC. In the .NET Framework days, service-to-service communication was also done using SOAP-based WCF services running on IIS. However, with that framework you could end up wrestling with XML-based messages and staying locked into the .NET ecosystem.&lt;/p&gt;
&lt;p&gt;Instead, gRPC—which runs over HTTP/2 and can operate much more efficiently, cross-platform, and cross-language—has become the most sensible RPC framework to choose for new projects.&lt;/p&gt;
&lt;h2 id="differences-between-rest-and-grpc"&gt;Differences between REST and gRPC&lt;/h2&gt;
&lt;p&gt;In a microservices architecture, RPC is not the only option for service-to-service communication. The REST API approach also enables services to communicate easily and widely with each other. However, there are notable differences between REST and gRPC in both architectural design and technical implementation. Let’s examine these differences under design philosophy and technical differences.&lt;/p&gt;
&lt;h3 id="1-design-philosophy"&gt;1. Design Philosophy&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt; and &lt;strong&gt;gRPC&lt;/strong&gt; take different perspectives in API design. In REST, the main approach is to see everything as a &lt;strong&gt;resource&lt;/strong&gt; and operate on those resources. HTTP methods (GET, POST, PUT, DELETE) represent the operation being performed, while the URL indicates the resource being acted upon. For example, &lt;code&gt;/api/books&lt;/code&gt; is a books resource. If you have an endpoint like this, you can more or less guess the operations you can do on it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /api/books&lt;/code&gt; returns the list of books,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/books&lt;/code&gt; adds a new book.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In &lt;strong&gt;gRPC&lt;/strong&gt;, the perspective is more &lt;strong&gt;action/method&lt;/strong&gt; oriented. Each operation is defined like a function call. In gRPC, you directly specify which operation you want to perform and you don’t stick to URL design or HTTP methods.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BookService.GetBooks()&lt;/code&gt; lists the books,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BookService.CreateBook()&lt;/code&gt; adds a new book.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While it’s technically possible to define action-style endpoints in REST, this generally doesn’t align with best practices or the standard REST philosophy.&lt;/p&gt;
&lt;h3 id="2-technical-differences"&gt;2. Technical Differences&lt;/h3&gt;
&lt;p&gt;REST and gRPC differ across many technical areas—from transport to data format, connection management to error handling. Below are the key topics and differences summarized:&lt;/p&gt;
&lt;h3 id="transport-layer"&gt;&lt;strong&gt;Transport Layer&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt; uses HTTP/1.1. A separate TCP/TLS connection is established for each REST API request. Especially at high request volumes, this can lead to performance bottlenecks such as &lt;strong&gt;head-of-line blocking&lt;/strong&gt; (one slow request affects the others). Also, in cases requiring continuous bidirectional communication (e.g., live data streams or real-time notifications), you need additional protocols such as WebSocket or Server-Sent Events. In other words, REST is fundamentally based on the request-response model and does not have native streaming support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;gRPC&lt;/strong&gt; works with the modern HTTP/2 protocol. Thanks to HTTP/2’s &lt;strong&gt;multiplexing&lt;/strong&gt; feature, many concurrent requests and responses can be carried over a single TCP connection. This provides high performance and low latency. With HTTP/2’s native streaming and header compression, gRPC makes real-time and bidirectional communication much easier and more efficient.&lt;/p&gt;
&lt;p&gt;gRPC typically defines service interfaces and message structures with &lt;strong&gt;Protocol Buffers&lt;/strong&gt; (Protobuf). The service definition explicitly specifies the remotely callable methods and message schemas.&lt;/p&gt;
&lt;p&gt;Here’s an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;service&lt;/span&gt; HelloService {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; SayHello (HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HelloRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; greeting &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HelloResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; reply &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;gRPC supports four different communication (RPC) patterns:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The classic pattern: the client sends one request, the server returns one response. Similar to REST’s request-response model. It can be defined as follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; SayHello(HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Server Streaming&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The client sends a single request, and the server can return a stream of multiple response messages. The client reads the server’s data stream one by one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; LotsOfReplies(HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Client Streaming&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The client sends multiple messages (a stream) to the server. After sending all messages, it receives a single response from the server. Useful for large uploads or batch updates.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; LotsOfGreetings(&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Bidirectional Streaming RPC&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Both client and server can independently send data streams to each other at the same time. The streams operate independently; ordering is preserved within each stream. Suitable for real-time chat applications or live data transfer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; BidiHello(&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="data"&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST:&lt;/strong&gt; Messages are typically carried in human-readable &lt;strong&gt;JSON&lt;/strong&gt; format. JSON is readable and flexible, but results in larger payload sizes and higher parsing cost. A strict schema is not required. Documentation is often added via OpenAPI/Swagger, but you can run into type mismatches at runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC:&lt;/strong&gt; Uses a binary and compressed format called &lt;strong&gt;Protocol Buffers (Protobuf)&lt;/strong&gt;. Protobuf messages are small and fast, reducing network and CPU cost. All data types and methods are defined in a &lt;code&gt;.proto&lt;/code&gt; file. Server and client code is generated automatically, providing strong type safety.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="performance-and-efficiency"&gt;&lt;strong&gt;Performance and Efficiency&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST:&lt;/strong&gt; Due to JSON’s larger size and the limitations of HTTP/1.1, latency and resource consumption increase under high traffic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC:&lt;/strong&gt; Thanks to small packet sizes and multiple requests over a single connection via HTTP/2, it provides lower latency, higher RPS (requests per second), and more efficient network usage.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="browser-and-platform-compatibility"&gt;&lt;strong&gt;Browser and Platform Compatibility&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST:&lt;/strong&gt; Can be called directly from the browser and easily integrated with fetch/AJAX.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC:&lt;/strong&gt; Cannot be used directly from the browser; it requires extra layers such as gRPC-Web or JSON Transcoding, so it’s not preferred in scenarios that include the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="which-scenarios-call-for-rest-or-grpc"&gt;Which scenarios call for REST or gRPC?&lt;/h2&gt;
&lt;p&gt;When choosing between REST and gRPC, looking only at technical details may not be sufficient; business needs, target users, and integration requirements are at least as important as performance. Here are some typical scenarios and suggestions:&lt;/p&gt;
&lt;h3 id="when-should-you-use-rest"&gt;When should you use REST?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Browser or third-party integration:&lt;/strong&gt; If your API will be consumed directly by web browsers or external applications, REST provides a more common and accessible solution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Public APIs:&lt;/strong&gt; Thanks to JSON’s readability and the widespread support for HTTP/1.1, REST-based APIs are easy for developers to test and document.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple CRUD operations:&lt;/strong&gt; For resource-oriented operations (e.g., products, orders) and classic GET/POST/PUT/DELETE flows, REST is simple and clear.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching/Proxy needs:&lt;/strong&gt; If you need to do caching or routing via CDN or reverse proxy, REST is more advantageous because it aligns fully with the HTTP standard.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="when-should-you-use-grpc"&gt;When should you use gRPC?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microservice-to-microservice communication:&lt;/strong&gt; For high-volume, low-latency service-to-service traffic within the same infrastructure, gRPC provides serious advantages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile and IoT devices:&lt;/strong&gt; The binary format and small message sizes reduce data consumption.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time streaming requirements:&lt;/strong&gt; For applications like chat, live scores, or notifications where server-to-client or bidirectional streaming is needed, gRPC offers native streaming support.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Language independence:&lt;/strong&gt; If you want strongly typed communication across multiple programming languages, Protobuf makes compatibility easy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stronger type safety:&lt;/strong&gt; With compiled contracts and code generation, the error rate in API consumption drops.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="comprehensive-net-example-books-service"&gt;Comprehensive .NET example: Books service&lt;/h2&gt;
&lt;p&gt;Let’s build a book management service using both REST and gRPC. In this example, we’ll also see the different streaming modes gRPC offers.&lt;/p&gt;
&lt;h3 id="project-setup"&gt;Project setup&lt;/h3&gt;
&lt;p&gt;First, add the required packages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new web -n BookService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd BookService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Grpc.AspNetCore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Grpc.AspNetCore.Server.Reflection
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Grpc.Tools
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="rest-api-implementation"&gt;REST API implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Models/Book.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Models;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Book&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Title { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Author { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; Year { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; Price { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Controllers/BooksController.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Mvc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Models;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Controllers;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;api/books&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BooksController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; List&amp;lt;Book&amp;gt; Books = Book.GenerateBooks();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult GetBooks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(Books);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult GetBook(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book = Books.FirstOrDefault(b =&amp;gt; b.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (book == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; NotFound();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult CreateBook([FromBody] Book book)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; book.Id = Books.Max(b =&amp;gt; b.Id) + &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Books.Add(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; CreatedAtAction(nameof(GetBook), &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; { id = book.Id }, book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="grpc-service--protobuf-definitions"&gt;gRPC service — Protobuf definitions&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Protos/books.proto
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;syntax&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;proto3&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;option&lt;/span&gt; csharp_namespace &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;BookService.Grpc&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; books;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Main service definition
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;service&lt;/span&gt; BookService {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Unary RPC - Get all books
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; GetBooks (GetBooksRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (BookList);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Server Streaming - Stream books one by one
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; StreamBooks (StreamBooksRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; Book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Client Streaming - Bulk add books
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; AddBooksStream (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; Book) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (AddBooksResponse);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Bidirectional Streaming - Real-time book search
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; SearchBooksStream (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; SearchRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; Book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Book&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; title &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; author &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; year &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; price &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetBooksRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Empty request message
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BookList&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;repeated&lt;/span&gt; Book books &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StreamBooksRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; delay_ms &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// Delay between each book (ms)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AddBooksResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; count &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SearchRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; query &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="grpc-service--implementation"&gt;gRPC service — Implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Services/GrpcBookService.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Grpc.Core;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Grpc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Services;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GrpcBookService&lt;/span&gt; : Grpc.BookService.BookServiceBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; List&amp;lt;BookService.Grpc.Book&amp;gt; Books = BookService.Models.Book.GenerateBooks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Select(b =&amp;gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BookService.Grpc.Book { Id = b.Id, Title = b.Title, Author = b.Author, Year = b.Year, Price = (&lt;span style="color:#66d9ef"&gt;double&lt;/span&gt;)b.Price })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .ToList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Unary RPC - Classic request-response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; Task&amp;lt;BookList&amp;gt; GetBooks(GetBooksRequest request, ServerCallContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BookList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response.Books.AddRange(Books);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Task.FromResult(response);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Server Streaming - Server streams books one by one&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task StreamBooks(StreamBooksRequest request,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IServerStreamWriter&amp;lt;BookService.Grpc.Book&amp;gt; responseStream,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerCallContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; Books)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Cancellation check&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (context.CancellationToken.IsCancellationRequested)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; responseStream.WriteAsync(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Wait for the specified duration (to demonstrate streaming)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (request.DelayMs &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Task.Delay(request.DelayMs);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Client Streaming - Client sends multiple books&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;AddBooksResponse&amp;gt; AddBooksStream(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IAsyncStreamReader&amp;lt;BookService.Grpc.Book&amp;gt; requestStream,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerCallContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; count = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; maxId = Books.Any() ? Books.Max(b =&amp;gt; b.Id) : &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; requestStream.ReadAllAsync())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; book.Id = ++maxId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Books.Add(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; count++;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AddBooksResponse
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Count = count,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Message = &lt;span style="color:#e6db74"&gt;$&amp;#34;Successfully added {count} books&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Bidirectional Streaming - Real-time two-way search&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SearchBooksStream(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IAsyncStreamReader&amp;lt;SearchRequest&amp;gt; requestStream,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IServerStreamWriter&amp;lt;BookService.Grpc.Book&amp;gt; responseStream,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerCallContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; searchRequest &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; requestStream.ReadAllAsync())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; results = Books.Where(b =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b.Title.Contains(searchRequest.Query, StringComparison.OrdinalIgnoreCase) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b.Author.Contains(searchRequest.Query, StringComparison.OrdinalIgnoreCase)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ).ToList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; results)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; responseStream.WriteAsync(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="programcs-configuration"&gt;Program.cs configuration&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Services;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// REST API services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddEndpointsApiExplorer();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSwaggerGen();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// gRPC services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddGrpc();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Swagger (for REST)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (app.Environment.IsDevelopment())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.UseSwagger();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.UseSwaggerUI();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// gRPC reflection (for development)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.MapGrpcReflectionService();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// REST endpoints&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// gRPC endpoints&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGrpcService&amp;lt;GrpcBookService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="csproj-file"&gt;.csproj file&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Project&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Sdk=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.NET.Sdk.Web&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net9.0&lt;span style="color:#f92672"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span style="color:#f92672"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span style="color:#f92672"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Grpc.AspNetCore&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;2.60.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Grpc.AspNetCore.Server.Reflection&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;2.60.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Grpc.Tools&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;2.60.0&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PrivateAssets&amp;gt;&lt;/span&gt;all&lt;span style="color:#f92672"&gt;&amp;lt;/PrivateAssets&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;IncludeAssets&amp;gt;&lt;/span&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;span style="color:#f92672"&gt;&amp;lt;/IncludeAssets&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PackageReference&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Swashbuckle.AspNetCore&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;6.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Protobuf&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Protos\\books.proto&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also, if your locally running app won’t run over HTTPS, you need to open a separate port for HTTP/2 apart from the HTTP/1 port. To do this, add the following definition in &lt;code&gt;appsettings.json&lt;/code&gt; to open the relevant ports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Kestrel&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Endpoints&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Http&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Protocols&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Http1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Url&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://*:5000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;gRPC&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Protocols&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Http2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Url&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://*:5001&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&lt;span style="color:#960050;background-color:#1e0010"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="grpc-client-examples"&gt;gRPC client examples&lt;/h2&gt;
&lt;p&gt;Now let’s look at client examples using different streaming modes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Client/GrpcClientExamples.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Grpc.Net.Client;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Grpc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Client;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GrpcClientExamples&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; GrpcChannel _channel;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; Grpc.BookService.BookServiceClient _client;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; GrpcClientExamples(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; serverAddress = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5001&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _channel = GrpcChannel .ForAddress(serverAddress, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GrpcChannelOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Disable HTTPS validation. Use carefully in production.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HttpHandler = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; HttpClientHandler
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _client = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Grpc.BookService.BookServiceClient(_channel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Unary RPC example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task UnaryCallExample()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;=== Unary Call Example ===&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; request = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GetBooksRequest();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _client.GetBooksAsync(request);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Received {response.Books.Count} books:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; response.Books)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;- {book.Title} by {book.Author} ({book.Year})&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Server streaming example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ServerStreamingExample()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;\\n=== Server Streaming Example ===&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; request = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StreamBooksRequest { DelayMs = &lt;span style="color:#ae81ff"&gt;500&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var call = _client.StreamBooks(request);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; call.ResponseStream.ReadAllAsync())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Received: {book.Title} - ${book.Price}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;Streaming completed&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Client streaming example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ClientStreamingExample()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;\\n=== Client Streaming Example ===&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var call = _client.AddBooksStream();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; booksToAdd = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;[]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Book { Title = &lt;span style="color:#e6db74"&gt;&amp;#34;Design Patterns&amp;#34;&lt;/span&gt;, Author = &lt;span style="color:#e6db74"&gt;&amp;#34;Gang of Four&amp;#34;&lt;/span&gt;, Year = &lt;span style="color:#ae81ff"&gt;1994&lt;/span&gt;, Price = &lt;span style="color:#ae81ff"&gt;59.99&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Book { Title = &lt;span style="color:#e6db74"&gt;&amp;#34;Head First Design Patterns&amp;#34;&lt;/span&gt;, Author = &lt;span style="color:#e6db74"&gt;&amp;#34;Eric Freeman&amp;#34;&lt;/span&gt;, Year = &lt;span style="color:#ae81ff"&gt;2004&lt;/span&gt;, Price = &lt;span style="color:#ae81ff"&gt;44.99&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Book { Title = &lt;span style="color:#e6db74"&gt;&amp;#34;Microservices Patterns&amp;#34;&lt;/span&gt;, Author = &lt;span style="color:#e6db74"&gt;&amp;#34;Chris Richardson&amp;#34;&lt;/span&gt;, Year = &lt;span style="color:#ae81ff"&gt;2018&lt;/span&gt;, Price = &lt;span style="color:#ae81ff"&gt;49.99&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; booksToAdd)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call.RequestStream.WriteAsync(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Sent: {book.Title}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Task.Delay(&lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Simulated delay&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call.RequestStream.CompleteAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Result: {response.Message}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Bidirectional streaming example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task BidirectionalStreamingExample()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;\\n=== Bidirectional Streaming Example ===&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var call = _client.SearchBooksStream();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Read results in the background&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; readTask = Task.Run(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; call.ResponseStream.ReadAllAsync())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Found: {book.Title} by {book.Author}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Send search queries&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; queries = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;[] { &lt;span style="color:#e6db74"&gt;&amp;#34;Clean&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Design&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Refactoring&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Domain&amp;#34;&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; query &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; queries)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Searching for: {query}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call.RequestStream.WriteAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SearchRequest { Query = query });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Task.Delay(&lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Wait after each search&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call.RequestStream.CompleteAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; readTask;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;Bidirectional streaming completed&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; ValueTask DisposeAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _channel.ShutdownAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _channel.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Usage example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Main(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;[] args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var client = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GrpcClientExamples();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.UnaryCallExample();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.ServerStreamingExample();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.ClientStreamingExample();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.BidirectionalStreamingExample();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="testing-with-grpcurl"&gt;Testing with grpcurl&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;grpcurl&lt;/code&gt; is a curl-like command-line tool for gRPC services. Let’s test our service:&lt;/p&gt;
&lt;h3 id="installing-grpcurl"&gt;Installing grpcurl&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# macOS (Homebrew)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install grpcurl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Linux&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Windows (Chocolatey)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;choco install grpcurl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="grpcurl-usage-examples"&gt;grpcurl usage examples&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. What services exist on the server? (Requires reflection)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grpcurl -plaintext localhost:5001 list
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Output:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# grpc.reflection.v1alpha.ServerReflection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. List the methods of BookService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grpcurl -plaintext localhost:5001 list books.BookService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Output:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService.AddBooksStream&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService.GetBooks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService.SearchBooksStream&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService.StreamBooks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3. Unary RPC call - Get all books&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grpcurl -plaintext localhost:5001 books.BookService/GetBooks
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Output:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;books&amp;#34;: [&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;id&amp;#34;: 1,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;title&amp;#34;: &amp;#34;Dune&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;author&amp;#34;: &amp;#34;Frank Herbert&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;year&amp;#34;: 1965,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;price&amp;#34;: 29.99&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# },&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="performance-comparison-rest-vs-grpc-benchmark"&gt;&lt;strong&gt;Performance comparison: REST vs gRPC benchmark&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;It’s nice to see the theoretical advantages, but let’s run a comprehensive benchmark to measure the real performance difference. Using BenchmarkDotNet, we’ll compare REST and gRPC across different scenarios.&lt;/p&gt;
&lt;h3 id="benchmark-setup"&gt;Benchmark setup&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// BookService.Benchmark/BookServiceBenchmark.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BenchmarkDotNet.Attributes;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Grpc.Net.Client;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Grpc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Net.Http.Json;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Benchmark;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[MemoryDiagnoser]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[SimpleJob(warmupCount: 3, iterationCount: 10)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BookServiceBenchmark&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; HttpClient? _httpClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; GrpcChannel? _grpcChannel;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; Grpc.BookService.BookServiceClient? _grpcClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; RestUrl = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5000&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GrpcUrl = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5001&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [GlobalSetup]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Setup()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// REST client setup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _httpClient = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; HttpClient { BaseAddress = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(RestUrl) };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// gRPC client setup - with HTTP/2 optimizations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _grpcChannel = GrpcChannel.ForAddress(GrpcUrl, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GrpcChannelOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MaxReceiveMessageSize = &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; * &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; * &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 4MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MaxSendMessageSize = &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; * &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; * &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 4MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _grpcClient = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Grpc.BookService.BookServiceClient(_grpcChannel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Benchmark(Baseline = true)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; REST_GetAllBooks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; books = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _httpClient!.GetFromJsonAsync&amp;lt;List&amp;lt;BookDto&amp;gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/api/books&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; books?.Count ?? &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; gRPC_GetAllBooks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _grpcClient!.GetBooksAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GetBooksRequest());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; response.Books.Count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; REST_GetAllBooks_10Times()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; totalCount = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &amp;lt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; books = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _httpClient!.GetFromJsonAsync&amp;lt;List&amp;lt;BookDto&amp;gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/api/books&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; totalCount += books?.Count ?? &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; totalCount;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; gRPC_GetAllBooks_10Times()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; totalCount = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &amp;lt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _grpcClient!.GetBooksAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GetBooksRequest());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; totalCount += response.Books.Count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; totalCount;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [GlobalCleanup]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Cleanup()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _httpClient?.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (_grpcChannel != &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _grpcChannel.ShutdownAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _grpcChannel.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="benchmark-results"&gt;Benchmark results&lt;/h3&gt;
&lt;p&gt;Actual performance results obtained after optimizations:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;StdDev&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;th&gt;RatioSD&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;&lt;strong&gt;REST_GetAllBooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;332.3 μs&lt;/td&gt;
&lt;td&gt;42.42 μs&lt;/td&gt;
&lt;td&gt;28.06 μs&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;22.91 KB&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gRPC_GetAllBooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;296.9 μs&lt;/td&gt;
&lt;td&gt;18.10 μs&lt;/td&gt;
&lt;td&gt;10.77 μs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.90&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.06&lt;/td&gt;
&lt;td&gt;25.66 KB&lt;/td&gt;
&lt;td&gt;1.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REST_GetAllBooks_10Times&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,257.1 μs&lt;/td&gt;
&lt;td&gt;364.16 μs&lt;/td&gt;
&lt;td&gt;240.87 μs&lt;/td&gt;
&lt;td&gt;9.85&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;227.61 KB&lt;/td&gt;
&lt;td&gt;9.94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gRPC_GetAllBooks_10Times&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,883.9 μs&lt;/td&gt;
&lt;td&gt;338.63 μs&lt;/td&gt;
&lt;td&gt;223.99 μs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.74&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.05&lt;/td&gt;
&lt;td&gt;255.17 KB&lt;/td&gt;
&lt;td&gt;11.14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="analysis-of-the-results"&gt;Analysis of the results&lt;/h3&gt;
&lt;p&gt;For single-request performance, gRPC runs about 10% faster than REST (296.9 μs vs 332.3 μs). This advantage stems from Protocol Buffers’ binary serialization format compared to JSON. In multiple-request scenarios, gRPC’s performance advantage becomes more pronounced, rising to about 11.5% (2,883.9 μs vs 3,257.1 μs). This increase can be explained by HTTP/2’s connection reuse.&lt;/p&gt;
&lt;p&gt;In terms of memory usage, gRPC consumes about 12% more memory compared to REST (25.66 KB vs 22.91 KB). This extra memory usage is due to the overhead required for HTTP/2 connection management. However, given the performance improvements, this memory increase can be considered an acceptable trade-off.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;This article compares &lt;strong&gt;REST and gRPC&lt;/strong&gt; for &lt;strong&gt;synchronous&lt;/strong&gt; inter-service communication in the .NET 9 ecosystem across architectural approach, technical underpinnings, and performance. REST, with JSON over HTTP/1.1, offers easy accessibility, browser compatibility, and caching/proxy advantages; gRPC provides lower latency and stronger type safety thanks to HTTP/2’s multiplexing and native &lt;strong&gt;streaming&lt;/strong&gt; capabilities plus Protobuf. The post includes both a REST controller and a gRPC service (unary, server/client/bidirectional streaming) in the same “Books” domain, along with &lt;strong&gt;grpcurl&lt;/strong&gt; test steps. BenchmarkDotNet results show that gRPC is roughly &lt;strong&gt;10%&lt;/strong&gt; faster on single calls and around &lt;strong&gt;11%&lt;/strong&gt; faster on consecutive calls than REST, at the cost of a small increase in memory usage.&lt;/p&gt;
&lt;p&gt;If you’d like access to the source code, you can find the entire project on my GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-grpc"&gt;GitHub - berkslv/lecture-grpc&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thanks for reading! 🎉 To keep up with my research in software development, follow &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Enforce Code Quality in .NET: .editorconfig &amp; Analyzers &amp; Pre-Commit Hooks</title><link>https://berkselvi.dev/posts/enforce-code-quality-in-net-editorconfig-analyzers-pre-commit-hooks/</link><pubDate>Thu, 24 Jul 2025 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/enforce-code-quality-in-net-editorconfig-analyzers-pre-commit-hooks/</guid><description>&lt;p&gt;I believe that maintaining and improving code quality is a responsibility we owe both to ourselves and to our teammates with every line of code we write. However, in practice, we sometimes overlook best practices due to hastily written code or tight deadlines. Additionally, we often face challenges when trying to spread these practices across our teams.&lt;/p&gt;
&lt;p&gt;To overcome such problems and to make code quality a requirement, rather than something left to individual developer discretion, I’d like to share a multi-step approach, proven by experience in a project with 20+ developers and 10+ services.&lt;/p&gt;</description><content>&lt;p&gt;I believe that maintaining and improving code quality is a responsibility we owe both to ourselves and to our teammates with every line of code we write. However, in practice, we sometimes overlook best practices due to hastily written code or tight deadlines. Additionally, we often face challenges when trying to spread these practices across our teams.&lt;/p&gt;
&lt;p&gt;To overcome such problems and to make code quality a requirement, rather than something left to individual developer discretion, I’d like to share a multi-step approach, proven by experience in a project with 20+ developers and 10+ services.&lt;/p&gt;
&lt;p&gt;First, we&amp;rsquo;ll use the TreatWarningsAsErrors flag to prevent the project from being released to production with 1000+ warnings.&lt;/p&gt;
&lt;p&gt;Then, with .editorconfig files, the conventions that .NET provides by default (e.g., property naming conventions) will become not just a guideline, but a rule applied across the team.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll further strengthen these rules using packages like SonarAnalyzer and Roslynator, allowing us to resolve issues before even running sonar scans, leading to more performant and secure code.&lt;/p&gt;
&lt;p&gt;Finally, with a pre-commit hook, we&amp;rsquo;ll catch unbuildable projects and failing tests before they even reach CI/CD pipelines, allowing us to see and fix these issues before committing. Let&amp;rsquo;s get started.&lt;/p&gt;
&lt;h2 id="warning-free-project-with-treatwarningsaserrors"&gt;Warning-Free Project with TreatWarningsAsErrors&lt;/h2&gt;
&lt;p&gt;When developing a project with .NET, the most common warning is &amp;ldquo;Possible null reference.&amp;rdquo; Of course, there can be many other warnings in our project. If we ignore them, it’s only natural to encounter NullReferenceExceptions everywhere at runtime.&lt;/p&gt;
&lt;p&gt;To resolve such errors before they even surface, we can treat warnings as errors and prevent the project from building if they exist. To do this, simply add the following flag to your .csproj file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;TreatWarningsAsErrors&amp;gt;&lt;/span&gt;true&lt;span style="color:#f92672"&gt;&amp;lt;/TreatWarningsAsErrors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;!-- Only if absolutely necessary --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;WarningsNotAsErrors&amp;gt;&lt;/span&gt;CS0108;NU1901;NU1902;NU1903;NU1904;NU1701&lt;span style="color:#f92672"&gt;&amp;lt;/WarningsNotAsErrors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, since this flag treats all warnings from the .NET platform as errors, some warnings, like those from Nuget packages that you can&amp;rsquo;t resolve yourself, can become a real headache. To address such issues, you can use the WarningsNotAsErrors flag, but only for errors you truly cannot fix.&lt;/p&gt;
&lt;h2 id="set-project-rules-with-editorconfig"&gt;Set Project Rules with .editorconfig&lt;/h2&gt;
&lt;p&gt;EditorConfig helps developers working on the same project maintain consistent coding styles across different IDEs. It’s not exclusive to .NET projects, it’s language-agnostic and includes both general and language-specific rules.&lt;/p&gt;
&lt;p&gt;With this, you can set project-level rules such as classes following PascalCase, variables using camelCase, and constants in LOWER_CASE all based on .NET’s &lt;a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/categories"&gt;standard guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition, using &lt;a href="https://rules.sonarsource.com/csharp/"&gt;SonarAnalyzer&lt;/a&gt;, you can catch problematic code like &lt;code&gt;name == &amp;quot;&amp;quot;&lt;/code&gt; (a disaster in code!) before it ever reaches a sonar scan, and get an instant suggestion (e.g., use &lt;code&gt;string.IsNullOrEmpty(name)&lt;/code&gt;) with Alt+Enter in Visual Studio. This way, your code will enter the CI/CD sonar scan step with far fewer issues.&lt;/p&gt;
&lt;p&gt;Another great code analyzer library, &lt;a href="https://josefpihrt.github.io/docs/roslynator/"&gt;Roslynator&lt;/a&gt;, lets you enforce more specific rules in your project, like requiring a blank line between class properties, which boosts code readability.&lt;/p&gt;
&lt;p&gt;Instead of adding these third-party analyzer packages to each project individually, you can create a &lt;code&gt;Directory.Build.Props&lt;/code&gt; file in your repo&amp;rsquo;s root directory with the following content. This will automatically include the packages in all projects during the build step:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Roslynator.Analyzers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.12.4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PrivateAssets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$(MSBuildProjectExtension) == &amp;#39;.csproj&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Roslynator.Formatting.Analyzers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.12.4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PrivateAssets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$(MSBuildProjectExtension) == &amp;#39;.csproj&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;SonarAnalyzer.CSharp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;9.32.0.97167&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PrivateAssets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$(MSBuildProjectExtension) == &amp;#39;.csproj&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here’s a sample .editorconfig file. The rules defined by the &lt;code&gt;IDE&lt;/code&gt; are standard .NET rules; those starting with &lt;code&gt;rcs&lt;/code&gt; come from Roslynator; those starting with &lt;code&gt;S&lt;/code&gt; are Sonar rules. These rules can be defined as suggestion, warning, or error, and can be customized for each project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# top-most EditorConfig file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Don&amp;#39;t use tabs for indentation.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_style &lt;span style="color:#f92672"&gt;=&lt;/span&gt; space
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# (Please don&amp;#39;t specify an indent_size here; that has too many unintended consequences.)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;spelling_exclusion_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; SpellingExclusions.dic
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Code files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;cs,csx,vb,vbx&lt;span style="color:#f92672"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;insert_final_newline &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;charset &lt;span style="color:#f92672"&gt;=&lt;/span&gt; utf-8-bom
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# XML project files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj&lt;span style="color:#f92672"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# XML config files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct&lt;span style="color:#f92672"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# JSON files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.json&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Powershell files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.ps1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Shell script files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.sh&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;end_of_line &lt;span style="color:#f92672"&gt;=&lt;/span&gt; lf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Dotnet code style settings:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;cs,vb&lt;span style="color:#f92672"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Sort using and Import directives with System.* appearing first&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_sort_system_directives_first &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_separate_import_directive_groups &lt;span style="color:#f92672"&gt;=&lt;/span&gt; false
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# IDE0005: Remove unnecessary using directives&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.IDE0005.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; warning
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# IDE0010: Add missing cases to switch statement&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.IDE0010.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; warning
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Add blank line after using directive list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.rcs0003.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; error
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Place new line after/before &amp;#39;?:&amp;#39; operator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.rcs0028.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; error
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Track uses of &amp;#34;TODO&amp;#34; tags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.S1135.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; suggestion
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, with these practices, we can make our code more consistent and readable by getting a suggestion like the one below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;csharp_prefer_braces &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true:suggestion
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Good:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (isActive)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; DoSomething();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Bad:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (isActive)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; DoSomething();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can also find references to .editorconfig in GitHub&amp;rsquo;s well-starred repos, such as &lt;a href="https://github.com/dotnet/roslyn/blob/main/.editorconfig"&gt;dotnet/roslyn&lt;/a&gt; and &lt;a href="https://github.com/christianhelle/refitter/blob/main/.editorconfig"&gt;christianhelle/refitter&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="catch-issues-locally-with-pre-commit-hooks"&gt;Catch Issues Locally with pre-commit Hooks&lt;/h2&gt;
&lt;p&gt;Pre-commit hooks are scripts that Git runs before every commit. They’re great for checks like code formatting, linting, or running tests, and they prevent problematic code from being committed. Blocking commits that fail tests can be annoying in the short term, but it’s crucial for keeping your tests reliable in the long run. If you have a test step in your CI/CD pipeline, this lets you see and fix issues locally before they ever reach that step.&lt;/p&gt;
&lt;p&gt;In .NET projects, you can set up these pre-commit hooks with helper packages, or you can ask every developer to run &lt;code&gt;git config core.hooksPath ./.git-hooks&lt;/code&gt; manually. Here, we&amp;rsquo;ll leverage .NET’s built-in capabilities without relying on extra packages.&lt;/p&gt;
&lt;p&gt;Add a simple MSBuild target like the one below to your &lt;code&gt;.csproj&lt;/code&gt; file. This ensures that your Git hook directory is set up during every &lt;code&gt;dotnet restore&lt;/code&gt; (and thus build) step, so anyone working on your project gets the pre-commit hook set up automatically. We skip this step in Production to avoid unnecessary installs in CI/CD or production environments.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Target&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Name=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;register git hooks&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BeforeTargets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Restore;CollectPackageReferences&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(ASPNETCORE_ENVIRONMENT)&amp;#39; != &amp;#39;Production&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Exec&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Command=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;git config core.hooksPath ./.git-hooks&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StandardOutputImportance=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Low&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StandardErrorImportance=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;High&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create a folder called &lt;code&gt;.git-hooks&lt;/code&gt; in your project&amp;rsquo;s root directory. Inside, create a file named &lt;code&gt;pre-commit&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;./git-hooks/pre-commit&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/sh
echo Running tests...
dotnet format
dotnet test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Don’t forget to make your file executable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod +x .git-hooks/pre-commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With this setup, you won’t be able to commit if your tests fail:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git commit -m &lt;span style="color:#e6db74"&gt;&amp;#39;failed commit&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Running tests...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Determining projects to restore...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; All projects are up-to-date &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; restore.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Committer.API -&amp;gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\s&lt;/span&gt;rc&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.API&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.API.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Committer.Tests -&amp;gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Test run &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests.dll &lt;span style="color:#f92672"&gt;(&lt;/span&gt;.NETCoreApp,Version&lt;span style="color:#f92672"&gt;=&lt;/span&gt;v9.0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;VSTest version 17.14.0 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;x64&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Starting test execution, please wait...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A total of &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; test files matched the specified pattern.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;xUnit.net 00:00:00.14&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Committer.Tests.Services.CalculatorServiceTests.Add_ShouldReturnCorrectSum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;a: 5, b: 3, expected: 7&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;FAIL&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Failed Committer.Tests.Services.CalculatorServiceTests.Add_ShouldReturnCorrectSum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;a: 5, b: 3, expected: 7&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Error Message:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.Equal&lt;span style="color:#f92672"&gt;()&lt;/span&gt; Failure: Values differ
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Expected: &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Actual: &lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Stack Trace:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Committer.Tests.Services.CalculatorServiceTests.Add_ShouldReturnCorrectSum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;Double a, Double b, Double expected&lt;span style="color:#f92672"&gt;)&lt;/span&gt; in C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\S&lt;/span&gt;ervices&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;alculatorServiceTests.cs:line &lt;span style="color:#ae81ff"&gt;25&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at InvokeStub_CalculatorServiceTests.Add_ShouldReturnCorrectSum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;Object, Span&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;1&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs&lt;span style="color:#f92672"&gt;(&lt;/span&gt;Object obj, BindingFlags invokeAttr, Binder binder, Object&lt;span style="color:#f92672"&gt;[]&lt;/span&gt; parameters, CultureInfo culture&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed! - Failed: 1, Passed: 18, Skipped: 0, Total: 19, Duration: &lt;span style="color:#ae81ff"&gt;39&lt;/span&gt; ms - Committer.Tests.dll &lt;span style="color:#f92672"&gt;(&lt;/span&gt;net9.0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git log --oneline
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;9a89b16 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;HEAD -&amp;gt; main, origin/main&lt;span style="color:#f92672"&gt;)&lt;/span&gt; test commit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10367df README.md added
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;0051724&lt;/span&gt; Inital commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If your tests pass, the commit will go through:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git commit -m &lt;span style="color:#e6db74"&gt;&amp;#39;succeded commit&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Running tests...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Determining projects to restore...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; All projects are up-to-date &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; restore.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Committer.API -&amp;gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\s&lt;/span&gt;rc&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.API&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.API.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Committer.Tests -&amp;gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Test run &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests.dll &lt;span style="color:#f92672"&gt;(&lt;/span&gt;.NETCoreApp,Version&lt;span style="color:#f92672"&gt;=&lt;/span&gt;v9.0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;VSTest version 17.14.0 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;x64&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Starting test execution, please wait...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A total of &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; test files matched the specified pattern.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Passed! - Failed: 0, Passed: 19, Skipped: 0, Total: 19, Duration: &lt;span style="color:#ae81ff"&gt;31&lt;/span&gt; ms - Committer.Tests.dll &lt;span style="color:#f92672"&gt;(&lt;/span&gt;net9.0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;main 0d9f3cf&lt;span style="color:#f92672"&gt;]&lt;/span&gt; succeded commit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; file changed, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; insertion&lt;span style="color:#f92672"&gt;(&lt;/span&gt;+&lt;span style="color:#f92672"&gt;)&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; deletion&lt;span style="color:#f92672"&gt;(&lt;/span&gt;-&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git log --oneline
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0d9f3cf &lt;span style="color:#f92672"&gt;(&lt;/span&gt;HEAD -&amp;gt; main&lt;span style="color:#f92672"&gt;)&lt;/span&gt; succeded commit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;9a89b16 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;origin/main&lt;span style="color:#f92672"&gt;)&lt;/span&gt; test commit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10367df README.md added
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;0051724&lt;/span&gt; Inital commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;In summary, we’ve looked at practices for making code quality a team-wide requirement in .NET projects, rather than leaving it to individual developer initiative. With TreatWarningsAsErrors, .editorconfig rules, and advanced analyzer packages, we increased both the readability and security of our code. Pre-commit hooks allowed us to block problematic or failing code from ever entering the repo. With these approaches, we established a sustainable, high-quality software development culture within the team.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like access to the source code, you can find the full project on my GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-net-pre-commit"&gt;github.com/berkslv/lecture-net-pre-commit&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 To stay updated with my research in software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Feature Flagging in .NET with OpenFeature and flagd</title><link>https://berkselvi.dev/posts/feature-flagging-in-dotnet-with-openfeature-and-flagd/</link><pubDate>Tue, 08 Jul 2025 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/feature-flagging-in-dotnet-with-openfeature-and-flagd/</guid><description>&lt;p&gt;Feature flagging has become a cornerstone of modern software delivery. If you’re deploying frequently, testing features with specific users, want safer releases, or need targeted rollouts, feature flags should be in your toolbox. In this post, I’ll introduce OpenFeature, a vendor-neutral feature flag standard, and show you how to integrate it in a .NET project with &lt;strong&gt;flagd&lt;/strong&gt; as your backend.&lt;/p&gt;
&lt;h2 id="what-is-feature-flagging"&gt;What is Feature Flagging?&lt;/h2&gt;
&lt;p&gt;Feature flags (a.k.a. feature toggles) let you turn features on or off at runtime, without deploying new code. You can even segment by user, environment, or any attribute you want to serve a feature. This unlocks:&lt;/p&gt;</description><content>&lt;p&gt;Feature flagging has become a cornerstone of modern software delivery. If you’re deploying frequently, testing features with specific users, want safer releases, or need targeted rollouts, feature flags should be in your toolbox. In this post, I’ll introduce OpenFeature, a vendor-neutral feature flag standard, and show you how to integrate it in a .NET project with &lt;strong&gt;flagd&lt;/strong&gt; as your backend.&lt;/p&gt;
&lt;h2 id="what-is-feature-flagging"&gt;What is Feature Flagging?&lt;/h2&gt;
&lt;p&gt;Feature flags (a.k.a. feature toggles) let you turn features on or off at runtime, without deploying new code. You can even segment by user, environment, or any attribute you want to serve a feature. This unlocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Progressive delivery (gradual rollouts)&lt;/li&gt;
&lt;li&gt;A/B testing and experimentation&lt;/li&gt;
&lt;li&gt;Instant kill switches for broken features&lt;/li&gt;
&lt;li&gt;Reduced risk during deployments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the core value? &lt;strong&gt;Control&lt;/strong&gt;. Suddenly, you can ship code that’s dark-launched, only visible to your team, or tested by a handful of beta users. If a problem occurs, you don’t scramble for a rollback; you just flip a flag.&lt;/p&gt;
&lt;h2 id="the-old-way-environment-variables"&gt;The Old Way: Environment Variables&lt;/h2&gt;
&lt;p&gt;Before I dive into OpenFeature and flagd, I want to share a bit of real-world pain. Like many teams, I started with &lt;strong&gt;environment variables&lt;/strong&gt; (&lt;code&gt;appsettings.json&lt;/code&gt; configs) to manage features. Maybe you recognize this pattern:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;EnableNewDashboard&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, inside your code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;EnableNewDashboard&amp;#34;&lt;/span&gt;] == &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Show the shiny new dashboard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It works—until it doesn’t. Here’s what usually goes wrong:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No targeting:&lt;/strong&gt; It’s all or nothing. Want to enable it just for internal users? Too bad.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No runtime change:&lt;/strong&gt; Changing an ENV variable often means redeploying or restarting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Growth of config sprawl:&lt;/strong&gt; You end up with a dozen flags, all mixed in with unrelated config.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No auditing or history:&lt;/strong&gt; Who changed what, and when?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No standardization:&lt;/strong&gt; Every app/team does it differently.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As teams scale, this approach becomes brittle. Progressive delivery, canary releases, and A/B tests all need dynamic control. This is the gap where feature flag platforms—and standards like OpenFeature—shine.&lt;/p&gt;
&lt;h2 id="why-openfeature"&gt;Why OpenFeature?&lt;/h2&gt;
&lt;p&gt;Most feature flag solutions (LaunchDarkly, Unleash, etc.) lock you into their SDKs and APIs. That’s where &lt;a href="https://openfeature.dev/"&gt;OpenFeature&lt;/a&gt; comes in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vendor agnostic:&lt;/strong&gt; Switch providers without rewriting your code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardized API:&lt;/strong&gt; Use the same approach across languages and stacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensible:&lt;/strong&gt; Add hooks for logging, telemetry, and more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud-native ready:&lt;/strong&gt; Built for modern architectures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With OpenFeature, your app code only depends on the OpenFeature SDK—not on a specific vendor or backend. If you ever outgrow your current provider or want to migrate to a managed service, it’s as simple as swapping the provider.&lt;/p&gt;
&lt;h2 id="why-flagd"&gt;Why flagd?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/open-feature/flagd"&gt;flagd&lt;/a&gt; is an open-source, lightweight feature flag backend. It’s designed to work out-of-the-box with OpenFeature and can serve flags from files, HTTP endpoints, or via gRPC to any application you need. But unfortunately there is no official UI to flagd right now, instead of the UI, flags managed with GitOps practices. flagd is ideal if you want a &lt;strong&gt;self-hosted&lt;/strong&gt;, cloud-native flag backend with zero vendor lock-in.&lt;/p&gt;
&lt;h2 id="real-life-example-from-env-to-feature-flags"&gt;Real-life Example: From ENV to Feature Flags&lt;/h2&gt;
&lt;p&gt;Let’s say you’re running a .NET web API. You’re launching a new feature, but want to enable it only for internal users before rolling it out to everyone. Rather than creating an environment variable and binding that to some variable like &lt;strong&gt;isInternalUser&lt;/strong&gt;, which needs re-deploying your code for every change, you want to change features in runtime, for specific segments you like.&lt;/p&gt;
&lt;p&gt;In a feature flag system that managed with the environment variable, if the Product Manager wants to A/B test a feature with 10% of the users, custom code and redeployment will be required.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Feature flags&lt;/strong&gt; flip this model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Toggle the feature for specific users/groups instantly.&lt;/li&gt;
&lt;li&gt;Roll out to everyone when ready—no deployment needed.&lt;/li&gt;
&lt;li&gt;If something breaks, instantly turn it off.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is how modern product teams iterate quickly, reduce risk, and deliver value continuously.&lt;/p&gt;
&lt;h2 id="integrating-openfeature-and-flagd-in-net"&gt;Integrating OpenFeature and flagd in .NET&lt;/h2&gt;
&lt;p&gt;Let’s see how to wire this up.&lt;/p&gt;
&lt;h3 id="1-add-nuget-packages"&gt;1. Add NuGet Packages&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package OpenFeature
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package OpenFeature.Contrib.Providers.Flagd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-run-flagd-with-docker-compose"&gt;2. Run flagd with docker-compose&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;flagd&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ghcr.io/open-feature/flagd:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./flags:/etc/flagd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;--uri&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;file:/etc/flagd/flags.json&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;--debug&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#39;8013:8013&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sample &lt;code&gt;flags.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;$schema&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://flagd.dev/schema/v0/flags.json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;flags&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;new-greeting&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;state&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ENABLED&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;variants&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;off&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;defaultVariant&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;off&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;targeting&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;if&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;$ref&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;userAgentIsFirefox&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;$evaluators&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;userAgentIsFirefox&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;in&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;firefox&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;var&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;userAgent&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-configure-openfeature-in-your-net-app"&gt;3. Configure OpenFeature in Your .NET App&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; OpenFeature.Contrib.Providers.Flagd;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; OpenFeature.Model;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; OpenFeature.Api.Instance.SetProviderAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FlagdProvider(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8013&amp;#34;&lt;/span&gt;)));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGet(&lt;span style="color:#e6db74"&gt;&amp;#34;/greet&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; context =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; client = OpenFeature.Api.Instance.GetClient();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Get User-Agent header&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; userAgent = context.Request.Headers[&lt;span style="color:#e6db74"&gt;&amp;#34;User-Agent&amp;#34;&lt;/span&gt;].FirstOrDefault() ?? &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; evalContext = EvaluationContext.Builder().Set(&lt;span style="color:#e6db74"&gt;&amp;#34;userAgent&amp;#34;&lt;/span&gt;, userAgent).Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; useNewGreeting = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.GetBooleanValueAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;new-greeting&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, evalContext);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; greeting = useNewGreeting
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ? &lt;span style="color:#e6db74"&gt;&amp;#34;Hey there! Welcome to the new greeting experience! 🎉&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; context.Response.WriteAsync(greeting);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-test"&gt;4. Test&lt;/h3&gt;
&lt;p&gt;Change the flag in your &lt;code&gt;flags.json&lt;/code&gt; file. Your .NET application will instantly detect the change. When we test the API endpoint, we’ll see results like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl http://localhost:5050/greet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Hello, World!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl http://localhost:5050/greet -H &lt;span style="color:#e6db74"&gt;&amp;#39;User-Agent: firefox&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Hey there! Welcome to the new greeting experience! 🎉
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want to send a direct request to the flagd backend and see the status of the flag, you can query it as shown below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -X POST &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8013/flagd.evaluation.v1.Service/ResolveBoolean&amp;#34;&lt;/span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;flagKey&amp;#34;:&amp;#34;new-greeting&amp;#34;,&amp;#34;context&amp;#34;:{}}&amp;#39;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;:false, &lt;span style="color:#e6db74"&gt;&amp;#34;reason&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;DEFAULT&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;variant&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;off&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;metadata&amp;#34;&lt;/span&gt;:&lt;span style="color:#f92672"&gt;{}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -X POST &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8013/flagd.evaluation.v1.Service/ResolveBoolean&amp;#34;&lt;/span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;flagKey&amp;#34;:&amp;#34;new-greeting&amp;#34;,&amp;#34;context&amp;#34;:{&amp;#34;userAgent&amp;#34;: &amp;#34;firefox&amp;#34;}}&amp;#39;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;:true, &lt;span style="color:#e6db74"&gt;&amp;#34;reason&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;TARGETING_MATCH&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;variant&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;metadata&amp;#34;&lt;/span&gt;:&lt;span style="color:#f92672"&gt;{}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Feature flags reduce risk, enable faster delivery, and improve user targeting. OpenFeature gives you a standardized, future-proof API for flag management in .NET. flagd is a solid, open-source backend, great for self-hosting and Kubernetes.&lt;/p&gt;
&lt;p&gt;If you’re still managing features with environment variables or configuration files, give OpenFeature and flagd a shot. Your future self will thank you, especially the next time you need to shut off a feature on Friday at 5pm.&lt;/p&gt;
&lt;p&gt;If you want to access the source code, you can find the whole project on my GitHub account:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-net-openfeature"&gt;https://github.com/berkslv/lecture-net-openfeature&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Real-Time Communication with SignalR: A Deep Dive</title><link>https://berkselvi.dev/posts/realtime-communication-with-signalr-a-deep-dive/</link><pubDate>Fri, 29 Nov 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/realtime-communication-with-signalr-a-deep-dive/</guid><description>&lt;p&gt;In today&amp;rsquo;s world where data is moving very quickly, real-time communication is a must for modern applications to give users smooth experiences. SignalR is an essential library when you want to create .NET application that communicates in real-time with others. When using SignalR, all communication and network requirements are abstracted by the library and you only need to call the method like in the RPC. By leveraging SignalR, you can easily create live dashboards, chat applications, or simple notification features that many web applications need. Let’s get started.&lt;/p&gt;</description><content>&lt;p&gt;In today&amp;rsquo;s world where data is moving very quickly, real-time communication is a must for modern applications to give users smooth experiences. SignalR is an essential library when you want to create .NET application that communicates in real-time with others. When using SignalR, all communication and network requirements are abstracted by the library and you only need to call the method like in the RPC. By leveraging SignalR, you can easily create live dashboards, chat applications, or simple notification features that many web applications need. Let’s get started.&lt;/p&gt;
&lt;h2 id="why-signalr"&gt;Why SignalR?&lt;/h2&gt;
&lt;p&gt;Traditional models of client-server communication revolve around the request-response model, which is considered less efficient and limited for high interaction needs, and requires the client to making a request when data is needed. SignalR is designed to eliminate these challenges by keeping the same connection alive from the client to the server.&lt;/p&gt;
&lt;h3 id="key-advantages-of-signalr"&gt;Key Advantages of SignalR:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real-Time Communication:&lt;/strong&gt; Clients receive the updates as soon as possible without necessarily making new requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-Platform Support:&lt;/strong&gt; SignalR now has a full range of functionality independent of any platform and browsers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Abstraction of Complex Protocols:&lt;/strong&gt; SignalR has the ability to use WebSocket, Server-Sent Events and Long Polling protocols, which provide a better relationship between the client and the server and allow them to choose the most efficient protocol. If the WebSocket protocol is not available by the client, SignalR tries to use other methods by selecting them according to its internal algorithm. (&lt;a href="https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr#transport-selection-process"&gt;https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr#transport-selection-process&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration with .NET apps:&lt;/strong&gt; Effective incorporation of .NET successfully guarantees the smooth through incorporation to modern .NET applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="getting-started-with-signalr"&gt;Getting Started with SignalR&lt;/h3&gt;
&lt;p&gt;To implement SignalR in your .NET project, you can follow these steps:&lt;/p&gt;
&lt;h3 id="1-signalr-nuget-package"&gt;1. SignalR NuGet Package&lt;/h3&gt;
&lt;p&gt;SignalR comes with .NET system libraries therefore you don’t need to install any additional packages. However if Visual Studio goes crazy you may want to try install the &lt;code&gt;Microsoft.AspNetCore.SignalR&lt;/code&gt; package.&lt;/p&gt;
&lt;h3 id="2-create-a-hub-class"&gt;2. Create a Hub Class&lt;/h3&gt;
&lt;p&gt;The Hub acts as the central component that manages connections and sends messages to clients. You can compare this class to Controllers where you accept HTTP requests, except here we can accept requests or make a request directly to the client. Here’s a simple example, clients invoke &lt;code&gt;SendNotification(string content)&lt;/code&gt; method to make a call to server and server send a message to all or specific clients with &lt;code&gt;Clients.All.SendAsync(&amp;quot;ReceiveNotification&amp;quot;, content)&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.SignalR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NotificationsHub&lt;/span&gt; : Hub
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotification(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Clients.All.SendAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;ReceiveNotification&amp;#34;&lt;/span&gt;, content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-configure-the-hub-in-programcs"&gt;3. Configure the Hub in &lt;code&gt;Program.cs&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Just register SignalR services and map the hub endpoint as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSignalR();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapHub&amp;lt;NotificationsHub&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;hubs/notification&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-signalr-client"&gt;4. SignalR Client&lt;/h3&gt;
&lt;p&gt;SignalR supports various client platforms, including Javascript, .NET, and Java. If you don’t want to implement SignalR library in that platforms, you can simply use Postman to test your endpoint. But you must insert &lt;strong&gt;0x1E&lt;/strong&gt; character at the end of the json. This is a requirement of the SignalR internal protocol. If you use Javascript library to implement SignalR you don’t need to deal with this kind of details.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;protocol&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After specifying the protocol, you can send and receive message from SignalR Hub as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;arguments&amp;#34;&lt;/span&gt; : [&lt;span style="color:#e6db74"&gt;&amp;#34;hello world&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;target&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;SendNotification&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;type&amp;#34;&lt;/span&gt; : &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here is a quick demo:&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/realtime-communication-with-signalr-a-deep-dive/postman-signalr-demo.gif" alt="SignalR and Postman demo" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;SignalR and Postman demo&lt;/p&gt;
&lt;h2 id="advanced-features"&gt;Advanced Features&lt;/h2&gt;
&lt;h3 id="1-strongly-typed-hubs"&gt;1. Strongly Typed Hubs&lt;/h3&gt;
&lt;p&gt;For better maintainability, use strongly typed hubs to specify interface of the hub:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;INotificationsClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task ReceiveNotification(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NotificationsHub&lt;/span&gt; : Hub&amp;lt;INotificationsClient&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotification(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Clients.All.ReceiveNotification(content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-targeting-specific-clients"&gt;2. Targeting Specific Clients&lt;/h3&gt;
&lt;p&gt;SignalR enables sending messages to specific users or groups. You can use this feature to send notification to only related ones, just add user to group using their userId or groupName, then send a notification to this userId:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Clients.User(userId).ReceiveNotification(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello, User!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Clients.Group(groupName).ReceiveNotification(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello, Group!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want to target specific clients with SignalR using userId, you have to implement &lt;code&gt;IUserIdProvider&lt;/code&gt; that coming from SignalR package like that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;UserIdProvider&lt;/span&gt; : IUserIdProvider
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetUserId(HubConnectionContext connection)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddSingleton&amp;lt;IUserIdProvider, CustomUserIdProvider&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-using-ihubcontext-to-access-the-hub-from-outside-the-hub"&gt;3. Using &lt;code&gt;IHubContext&lt;/code&gt; to access the hub from outside the hub&lt;/h3&gt;
&lt;p&gt;You can send message to clients in outside of the hub as follows. Just Inject the IHubContext of type, and send a message to them.&lt;/p&gt;
&lt;p&gt;You can trigger the hub when some event is occured in your system with this greate feature. For example, when a long running batch job is completed you can notify your users about the result.&lt;/p&gt;
&lt;p&gt;Just a quick note, your code can be much cleaner if you abstract this IHubContext interface with another interface like &lt;code&gt;INotificationDispatcher&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NotificationDispatcher&lt;/span&gt;(IHubContext&amp;lt;NotificationsHub&amp;gt; hubContext) : INotificationDispatcher
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotification(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; hubContext.Clients.All.SendAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;ReceiveNotification&amp;#34;&lt;/span&gt;, content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotificationToUser(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; userId, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; hubContext.Clients.User(userId).SendAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;ReceiveNotification&amp;#34;&lt;/span&gt;, content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotificationToGroup(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; groupName, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; hubContext.Clients.Group(groupName).SendAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;ReceiveNotification&amp;#34;&lt;/span&gt;, content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-securing-signalr"&gt;4. Securing SignalR&lt;/h3&gt;
&lt;p&gt;By default, SignalR hubs can be accessed by any client. To restrict access, use the classic &lt;code&gt;[Authorize]&lt;/code&gt; attribute. Ensure that users authenticate using mechanisms like JWT, which SignalR leverages to identify connected users:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Authorize]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NotificationsHub&lt;/span&gt; : Hub
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Hub methods&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="5-scaling-signalr-with-redis-backplane"&gt;5. Scaling SignalR with Redis backplane&lt;/h3&gt;
&lt;p&gt;Scaling SignalR across multiple servers ensures reliable real-time communication for high-demand applications that running in multiple instances. A &lt;strong&gt;Redis backplane&lt;/strong&gt; synchronizes messages between SignalR servers, ensuring clients connected to different servers receive messages seamlessly. Here is a quote from microsoft docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The SignalR Redis backplane uses the pub/sub feature to forward messages to other servers. When a client makes a connection, the connection information is passed to the backplane. When a server wants to send a message to all clients, it sends to the backplane. The backplane knows all connected clients and which servers they&amp;rsquo;re on. It sends the message to all clients via their respective servers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSignalR()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddStackExchangeRedis(&lt;span style="color:#e6db74"&gt;&amp;#34;localhost:6379&amp;#34;&lt;/span&gt;, options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.Configuration.ChannelPrefix = &lt;span style="color:#e6db74"&gt;&amp;#34;SignalR&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you don’t belive me, you can try to run your signalr app in more than one instance in the local testing enviroment with docker compose:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;signalr&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;your-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;5000:5000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;redis&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;redis:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;6379:6379&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose up --scale signalr&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="signalr-communication-models"&gt;SignalR Communication Models&lt;/h2&gt;
&lt;p&gt;SignalR abstracts multiple transport methods to ensure compatibility and performance:&lt;/p&gt;
&lt;h3 id="websocket-websockets"&gt;&lt;strong&gt;WebSocket (&lt;/strong&gt;&lt;code&gt;webSockets&lt;/code&gt;&lt;strong&gt;)&lt;/strong&gt;:&lt;/h3&gt;
&lt;p&gt;WebSockets is the most efficient and preferred transport for real-time, bi-directional communication in SignalR. It establishes a persistent, full-duplex connection over a single TCP channel, allowing simultaneous message exchange between the client and server. This makes it ideal for applications that require low-latency, high-throughput interactions, such as live chats, collaborative editing tools, online gaming, and financial tickers.&lt;/p&gt;
&lt;p&gt;WebSockets transitions to its own lightweight protocol (&lt;code&gt;ws://&lt;/code&gt; or &lt;code&gt;wss://&lt;/code&gt;) after an initial HTTP handshake, reducing the overhead associated with traditional HTTP-based communication. However, scaling WebSockets requires specialized infrastructure, such as WebSocket-aware load balancers and sticky sessions, to maintain connection affinity. SignalR simplifies these complexities, managing connection persistence, automatic reconnections, and fallback to alternative protocols when WebSockets are unavailable.&lt;/p&gt;
&lt;h3 id="server-sent-events-serversentevents"&gt;&lt;strong&gt;Server-Sent Events (&lt;/strong&gt;&lt;code&gt;serverSentEvents&lt;/code&gt;&lt;strong&gt;):&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;SSE provides a lightweight, HTTP-based mechanism for one-way communication, where the server streams real-time updates to the client over a long-lived connection. It uses the standardized EventSource API and transmits data formatted as &lt;code&gt;text/event-stream&lt;/code&gt;. This makes SSE simple to implement and efficient for use cases like live dashboards, notifications, and news feeds, where only server-to-client communication is needed.&lt;/p&gt;
&lt;p&gt;Because it operates over standard HTTP, SSE integrates easily with existing HTTP infrastructure and does not require specialized proxies or load balancers, making it easier to scale compared to WebSockets. However, it lacks support for Internet Explorer and does not natively allow client-to-server communication, which must be handled through separate HTTP requests. SignalR automatically leverages SSE when WebSockets is unavailable, providing a seamless fallback for environments with restricted capabilities.&lt;/p&gt;
&lt;h3 id="long-polling-longpolling"&gt;&lt;strong&gt;Long Polling (&lt;/strong&gt;&lt;code&gt;longPolling&lt;/code&gt;&lt;strong&gt;):&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Long Polling emulates real-time communication by continuously opening and closing HTTP connections. The client sends a request to the server and keeps it open until the server has data to send. Once the data is delivered, the client immediately establishes a new request. This approach minimizes latency between updates and creates the appearance of a push-based connection.&lt;/p&gt;
&lt;p&gt;While Long Polling works with all browsers and is easy to implement, it incurs more overhead due to frequent HTTP requests and responses. It is less efficient compared to persistent connections like WebSockets or SSE, especially for high-frequency updates. In SignalR, Long Polling serves as a fallback when neither WebSockets nor SSE is supported, ensuring compatibility across legacy systems and restricted environments.&lt;/p&gt;
&lt;h3 id="forever-frame-foreverframe"&gt;&lt;strong&gt;Forever Frame (&lt;/strong&gt;&lt;code&gt;foreverFrame&lt;/code&gt;&lt;strong&gt;):&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Forever Frame is a legacy protocol specific to older versions of Internet Explorer, designed to enable real-time, one-way communication. It operates by embedding a hidden iframe in the client’s browser that maintains an open connection to the server. The server continuously streams executable scripts to the iframe, which are executed by the client to process incoming data.&lt;/p&gt;
&lt;p&gt;While functional for its time, Forever Frame has significant limitations, including high overhead and lack of bi-directional communication. Any client-to-server interaction requires separate standard HTTP requests, making it less efficient and cumbersome to implement. SignalR includes Forever Frame for backward compatibility but uses it only as a last-resort fallback in environments restricted to legacy Internet Explorer browsers.&lt;/p&gt;
&lt;h3 id="websockets-vs-server-sent-events"&gt;Websockets vs Server-Sent Events&lt;/h3&gt;
&lt;p&gt;WebSockets and Server-Sent Events (SSE) cater to different real-time communication needs. &lt;strong&gt;WebSockets&lt;/strong&gt; enable full-duplex, low-latency communication, ideal for chat, gaming, and collaborative tools. In contrast, &lt;strong&gt;SSE&lt;/strong&gt; provides lightweight, one-way updates, perfect for live dashboards or notifications.&lt;/p&gt;
&lt;p&gt;WebSockets use a custom protocol (&lt;code&gt;ws://&lt;/code&gt; or &lt;code&gt;wss://&lt;/code&gt;) for efficient two-way messaging but require WebSocket-aware infrastructure for scaling. SSE operates over standard HTTP (&lt;code&gt;text/event-stream&lt;/code&gt;), making it easier to scale with traditional HTTP tools, though it only supports server-to-client communication and lacks Internet Explorer support.&lt;/p&gt;
&lt;p&gt;SignalR abstracts these differences, automatically prioritizing WebSockets for performance and falling back to SSE or other methods to ensure seamless real-time functionality across varied environments.&lt;/p&gt;
&lt;h2 id="specifiying-the-protocol"&gt;Specifiying the protocol&lt;/h2&gt;
&lt;p&gt;SignalR allows you to explicitly specify the transport protocol to be used for real-time communication. By default, SignalR automatically selects the best available transport with its internal algorithm, but you can customize this behavior based on your application&amp;rsquo;s needs.&lt;/p&gt;
&lt;p&gt;You can choose WebSockets for its efficiency and fall back to Long Polling when WebSockets are unavailable with that code in Program.cs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapHub&amp;lt;NotificationsHub&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/hubs/notification&amp;#34;&lt;/span&gt;, opt =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; opt.Transports = HttpTransportType.WebSockets | HttpTransportType.LongPolling;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or in the client side with JavaScript:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;connection&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;signalR&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HubConnectionBuilder&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;withUrl&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/hubs/notification&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;transport&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;signalR&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HttpTransportType&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WebSockets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;signalR&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HttpTransportType&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;LongPolling&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;build&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;SignalR makes adding real-time features to .NET applications simple and efficient. It abstracts complex networking protocols, letting you focus on building live dashboards, chat apps, or notifications without worrying about the underlying transport. With support for WebSockets, Server-Sent Events (SSE), and Long Polling, SignalR automatically picks the best protocol for your environment.&lt;/p&gt;
&lt;p&gt;If you want to access the source code, you can find the whole project on my GitHub account:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-signalr-deep-dive"&gt;GitHub - berkslv/lecture-signalr-deep-dive&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Event-Driven Architecture: Do you need other service’s data in microservice architecture</title><link>https://berkselvi.dev/posts/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/</link><pubDate>Sat, 07 Sep 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/</guid><description>&lt;p&gt;In applications developed with microservice architecture, domains are generally tried to be strictly separated from each other. In the example we will examine, all logic and data related to the product are located in the product service, while the organization service that manages the sales consultants who can create orders with this product also hosts its own logic and data.&lt;/p&gt;
&lt;p&gt;However, the &lt;code&gt;createUnit()&lt;/code&gt; request we will make to create a sales consultant in the organization service will need the data of the product service for validation reasons. Shouldn&amp;rsquo;t the unit be given the permission to create a product that doesn&amp;rsquo;t exist? In this case, the organization service will need the data of the product service, which is not in its domain and is not under its responsibility.&lt;/p&gt;</description><content>&lt;p&gt;In applications developed with microservice architecture, domains are generally tried to be strictly separated from each other. In the example we will examine, all logic and data related to the product are located in the product service, while the organization service that manages the sales consultants who can create orders with this product also hosts its own logic and data.&lt;/p&gt;
&lt;p&gt;However, the &lt;code&gt;createUnit()&lt;/code&gt; request we will make to create a sales consultant in the organization service will need the data of the product service for validation reasons. Shouldn&amp;rsquo;t the unit be given the permission to create a product that doesn&amp;rsquo;t exist? In this case, the organization service will need the data of the product service, which is not in its domain and is not under its responsibility.&lt;/p&gt;
&lt;p&gt;We can access this data either synchronously or asynchronously. First, we’ll look at accessing it synchronously and the problems we may encounter, then we’ll address the potential issues with using the asynchronous method. Let’s begin.&lt;/p&gt;
&lt;h2 id="synchronous-communication"&gt;Synchronous communication&lt;/h2&gt;
&lt;p&gt;With the request we will send to our organization service via REST, we will trigger the &lt;code&gt;createUnit()&lt;/code&gt; method and create a unit. However, since this service will need product data for validation purposes, it will need to retrieve this data synchronously through an HTTP REST &lt;code&gt;getProduct()&lt;/code&gt; request.&lt;/p&gt;
&lt;p&gt;During runtime, for the &lt;code&gt;createUnit()&lt;/code&gt; method to be processed, both the organization service and the product service need to be up and accessible, and there must be no issues with the network connection between them. If the product service is not operational, the organization service will also not function. Even when both services are up and accessible, if the product service is slow, the synchronous request flow will be blocked, causing the organization service to slow down as well. To overcome this issue, the organization service will need to scale during periods of high demand. However, since the product service has a runtime dependency, it will also need to be scaled in the same way to handle the large number of requests coming from the organization. If we apply this practice to all services, all services will need to scale together. As a result, the organization service will be highly dependent on the product service, and they will have to continue operating together rather than independently. If all the services in our microservice architecture become dependent on each other in this way, the inaccessibility of a single service could lead to all other services becoming inaccessible as well.&lt;/p&gt;
&lt;p&gt;During design time, if a change is made to the &lt;code&gt;getProduct()&lt;/code&gt; API in the product service, the developers of the organization service would also need to update their HTTP client integrations accordingly. This change would likely require modifying the organization service&amp;rsquo;s codebase as well.&lt;/p&gt;
&lt;p&gt;In conclusion, since coupling will occur both at runtime and design time, the two services will be tightly coupled, leading to what Jonathan Tower calls a &lt;a href="https://www.youtube.com/watch?v=p2GlRToY5HI&amp;amp;t=1203s"&gt;distributed monolith&lt;/a&gt;. Any change in one service will directly affect the other, making it difficult to quickly respond to constantly changing business requirements. Any improvements made to the &lt;code&gt;getProduct()&lt;/code&gt; method in the product service would also need to be implemented by the other services that consume it. After completing these updates, all services connected synchronously must be accessible for end-users to use the application.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/synchronous-communication.png" alt="Synchronous communication" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Synchronous communication&lt;/p&gt;
&lt;p&gt;In a scenario where we want to guarantee an SLA to our customer, if we optimistically offer a 99.5% uptime guarantee for both the product and organization services, we are accepting that there could be up to 43 hours of downtime annually. In a scenario where the product and organization services are dependent on each other during runtime, using the calculation $0.995 * 0.995 = 0.990$, the dependency created by synchronous communication results in our services offering twice the downtime guarantee, potentially leading to 87 hours of downtime annually.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Organization&lt;/th&gt;
&lt;th&gt;Product &amp;amp; Organization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Uptime percentage&lt;/td&gt;
&lt;td&gt;99.5&lt;/td&gt;
&lt;td&gt;99.5&lt;/td&gt;
&lt;td&gt;99.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Month&lt;/td&gt;
&lt;td&gt;716&lt;/td&gt;
&lt;td&gt;716&lt;/td&gt;
&lt;td&gt;712&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downtime in month&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Year&lt;/td&gt;
&lt;td&gt;8716&lt;/td&gt;
&lt;td&gt;8716&lt;/td&gt;
&lt;td&gt;8672&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downtime in year&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So, how do we avoid this method, which can cause so many headaches? Let&amp;rsquo;s look at another solution for communication between services.&lt;/p&gt;
&lt;h2 id="asynchronous-communication"&gt;Asynchronous communication&lt;/h2&gt;
&lt;p&gt;If we don’t want to choose synchronous communication, which increases inter-service dependency in every possible direction, we can proceed with the alternative: asynchronous communication. By reversing the arrows with this method, the product service publishes a domain event called &lt;code&gt;ProductCreatedEvent&lt;/code&gt; whenever there is an update in the product table. All services listening to this event update their own local &lt;code&gt;products&lt;/code&gt; tables.&lt;/p&gt;
&lt;p&gt;When the organization service needs product data, it reads from its own &lt;code&gt;products&lt;/code&gt; table. This table is unaffected by the organization&amp;rsquo;s workflows and is only read from. This way, in the &lt;code&gt;createUnit()&lt;/code&gt; method, we query the product data from the same database where we save the unit data.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/asynchronous-communication.png" alt="Asynchronous communication" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Asynchronous communication&lt;/p&gt;
&lt;p&gt;With this method, we access the product data from the organization&amp;rsquo;s database in the &lt;code&gt;createUnit()&lt;/code&gt; flow, eliminating runtime dependency on the product service.&lt;/p&gt;
&lt;p&gt;By eliminating runtime dependency, we stop making synchronous queries to the product service’s &lt;code&gt;getProduct()&lt;/code&gt; endpoint. At design time, the dependency we faced while developing is also removed. After making a change in the &lt;code&gt;getProduct()&lt;/code&gt; method of the product service, the team managing the organization service no longer needs to make any changes.&lt;/p&gt;
&lt;p&gt;During design time, we only have an asynchronous dependency, and we only need to make changes when necessary. If a new column is added to the product domain model, it can be added directly to the event as a new field. If a column is removed from the product table, it can be sent with default values without being removed from the event type and marked as obsolete. Services can respond to these added or removed columns as needed or continue without making any changes.&lt;/p&gt;
&lt;p&gt;The product service does not need to care about who is processing the published event. Anyone who needs this data can consume the event, maintain their own copy of the product data, and carry on their work without having to synchronize communication with the product service. This leads to more scalable and accessible applications since they can now operate independently without depending on other services.&lt;/p&gt;
&lt;p&gt;If we worry that our tables will mix domains, we can keep tables from different domains in a separate database schema as shown in the example below.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/asynchronous-communication-2.png" alt="Asynchronous communication 2" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Asynchronous communication 2&lt;/p&gt;
&lt;h2 id="eventual-consistency"&gt;Eventual consistency&lt;/h2&gt;
&lt;p&gt;The biggest issue that may arise from this method is eventual consistency. The update of the relevant table in the organization service may not reflect at the exact same moment a product is added to the product service. Depending on the availability of the message broker, there could be a delay of a few milliseconds or seconds.&lt;/p&gt;
&lt;p&gt;We can also explain this situation with CAP theory. If we keep the Partition tolerance leg, which is one of the 3 legs of CAP theory, constant, one of the Consistency and Availability legs will increase while the other will decrease. Therefore, we will not be able to provide these three options at the same time. Here, we need to decide according to the workflow we are working with.&lt;/p&gt;
&lt;p&gt;For example, in an e-commerce system, the &amp;ldquo;add to cart&amp;rdquo; feature might need to be highly available. If the &amp;ldquo;add to cart&amp;rdquo; feature isn’t working, receiving an error message like “please try again later” might cause customers to switch to a competitor&amp;rsquo;s site. However, if we see 2 instead of 1 of the same item in our cart, we can simply adjust our cart and continue the payment process.&lt;/p&gt;
&lt;p&gt;However, the same approach may not work for hotel reservations. In contrast to the cart example, consistency is probably more critical. Returning an error message like &amp;ldquo;please try again later&amp;rdquo; is much more logical than renting out the same room to multiple customers.&lt;/p&gt;
&lt;p&gt;In conclusion, synchronous communication is suitable for workflows that need immediate consistency, while asynchronous communication is better for workflows that require high availability. Typically, in microservice architecture, high availability is preferred, as synchronous communication tightly couples all services together.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/high-avalability-vs-immidiate-consistency.png" alt="High availability vs Immediate consistency" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;High availability vs Immediate consistency&lt;/p&gt;
&lt;h2 id="edge-cases-in-asynchronous-communication"&gt;Edge cases in asynchronous communication&lt;/h2&gt;
&lt;p&gt;If you have decided to proceed with event-driven architecture in the communication between your services, you will need to do something to ensure data consistency in the system. If a create request comes to one of our services and we publish CreatedEvent, we first update the database and then publish the event, if the transaction we execute in the database results in an error, and we publish the event without following it, or if the transaction we execute in the database results in success but the event bus cannot be accessed afterwards, the data that is distributed in our applications may fall into an inconsistent state. In order to prevent this situation, while the product service updates the product data in its own database, it also adds the relevant event to the outbox table in the same database, and then a different thread continuously scans this table and publishes the new events. Since the record we make to the database will be processed atomically, it is guaranteed that the event data will be created in the outbox table while the product data is being created. However, in an application that works as multiple instances, situations such as an event reading from the same outbox table twice and publishing it twice may be encountered. In this case, the idempotency problem already arises.&lt;/p&gt;
&lt;p&gt;If the result of a method being called once is the same as the result of multiple calls, then this is an idempotent method. In order to prevent an event from being processed multiple times due to different reasons and thus disrupting data consistency in the system, we will need to save the unique message id values ​​of the events to the database after they are consumed, and continue processing if the message id value is not in the database when a new event is to be consumed. Chris Richardson (microservices.io) also touches on this issue in his article Pattern: &lt;a href="https://microservices.io/patterns/communication-style/idempotent-consumer.html"&gt;Idempotent Consumer&lt;/a&gt;.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/without-outbox-idempotency.png" alt="Without Outbox &amp; Idempotency" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Without Outbox &amp; Idempotency&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/outbox-idempotency.png" alt="Outbox &amp; Idempotency" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Outbox &amp; Idempotency&lt;/p&gt;
&lt;p&gt;As a result, as with most solutions to problems in the field of Computer Science, we do not have a silver bullet. After considering the advantages and disadvantages of both methods in detail and in the domain we are working in, we can continue with the method we want. However, my suggestion would be to eliminate the dependency between services by using asynchronous communication whenever possible.&lt;/p&gt;
&lt;p&gt;You can access the .NET project I developed to simulate situations that may occur while working with asynchronous and synchronous data communication from the github repo below. In the example, the infrastructure provided by MassTransit was used for the outbox structure, and no special development was made for this feature.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-fetch-other-service-data-with-event-driven-architecture"&gt;GitHub - berkslv/lecture-fetch-other-service-data-with-event-driven-architecture&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=p2GlRToY5HI&amp;amp;t=1203s"&gt;Don’t Build a Distributed Monolith - Jonathan &amp;ldquo;J.&amp;rdquo; Tower - NDC London 2023&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=STKCRSUsyP0"&gt;The Many Meanings of Event-Driven Architecture • Martin Fowler • GOTO 2017&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=AEbJgpamZ4w&amp;amp;t=898s"&gt;Solving distributed data problems in a microservice architecture | Microservices.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=rZxIzrjvSGg&amp;amp;t=716s"&gt;You Keep Using That Word • Sam Newman • GOTO 2023&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://microservices.io/patterns/communication-style/idempotent-consumer.html"&gt;Microservices Pattern: Pattern: Idempotent Consumer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.reactivemanifesto.org/en"&gt;The Reactive Manifesto&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>CorrelationId Tracing in .NET Microservices with HTTP and MassTransit</title><link>https://berkselvi.dev/posts/correlationid-tracing-net-microservices-with-http-and-rabbitmq/</link><pubDate>Sun, 14 Jul 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/correlationid-tracing-net-microservices-with-http-and-rabbitmq/</guid><description>&lt;p&gt;CorrelationId has a very important place under the title of Distributed Tracing in order to be able to trace the ongoing process during the communication between applications running distributed in the microservice architecture and to understand which steps came to this point if there is a performance-related or flow problem in a service.&lt;/p&gt;
&lt;p&gt;CorrelationId must be carried throughout the entire lifecycle of a request, ensuring the correlation between different services. In this article, we will examine how to navigate the CorrelationId value using HTTP and RabbitMQ requests in a .NET based microservice architecture. This solution was designed to be a clear answer to our problem, but it is open to any comments and improvements, if you have a better solution in mind, please contact me.&lt;/p&gt;</description><content>&lt;p&gt;CorrelationId has a very important place under the title of Distributed Tracing in order to be able to trace the ongoing process during the communication between applications running distributed in the microservice architecture and to understand which steps came to this point if there is a performance-related or flow problem in a service.&lt;/p&gt;
&lt;p&gt;CorrelationId must be carried throughout the entire lifecycle of a request, ensuring the correlation between different services. In this article, we will examine how to navigate the CorrelationId value using HTTP and RabbitMQ requests in a .NET based microservice architecture. This solution was designed to be a clear answer to our problem, but it is open to any comments and improvements, if you have a better solution in mind, please contact me.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/correlationid-tracing-net-microservices-with-http-and-rabbitmq/big-picture.png" alt="Big picture" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Big picture&lt;/p&gt;
&lt;p&gt;We will manipulate the CorrelationId value between services by manipulating the header values ​​by intervening in incoming and outgoing requests with Middleware and Filter structures during communication between services using HTTP and MassTransit. During this travel between services, we will use the LogContext class provided by Serilog to log the contexts initiated by incoming requests with the given CorrelationId value. You can access all examples from the repo below.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-correlation-id-microservices"&gt;GitHub - berkslv/lecture-correlation-id-microservices&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We can use our Correlation class, which we could define as Scoped, to send the CorrelationId value we receive in incoming requests to our service in outgoing requests. However, in this way, our state value, which we move between classes with Dependency Injection methods, becomes inaccessible in a special case in our request that proceeds asynchronously: Since our &lt;code&gt;CorrelationHeaderHandler&lt;/code&gt; class, which will intervene in the requests we make with the HttpClient using the DelegatingHandler class, will work in a separate DI scope from our application, the value of our Correlation class, which we could define as Scoped, becomes inaccessible in a this special case. This issue is also explained in Microsoft&amp;rsquo;s own documentation as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When IHttpClientFactory creates a new delegating handler, it uses DI to fulfill the handler&amp;rsquo;s constructor parameters. IHttpClientFactory creates a separate DI scope for each handler, which can lead to surprising behavior when a handler consumes a scoped service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a solution to this problem, we will create a class called &lt;code&gt;AsyncStorage&lt;/code&gt; and use the &lt;code&gt;AsyncLocal&lt;/code&gt; class provided by Microsoft to create a structure that we can access at any time within the same thread in our async request to set the CorrelationId value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Stores and retrieves values in an async context.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;typeparam name=&amp;#34;T&amp;#34;&amp;gt;What should be stored&amp;lt;/typeparam&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AsyncStorage&lt;/span&gt;&amp;lt;T&amp;gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; T : &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; AsyncLocal&amp;lt;T&amp;gt; _asyncLocal = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AsyncLocal&amp;lt;T&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; T Store(T val)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _asyncLocal.Value = val;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _asyncLocal.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; T? Retrieve()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _asyncLocal.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We will define the Correlation class, which we will provide as a type to the AsyncStorage class, as shown below. The Id property will contain the CorrelationId value.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// It holds the CorrelationId value that comes with HTTP requests and events handled via MassTransit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Correlation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;init&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since we are storing the CorrelationId value in a location that is accessible throughout the request, let&amp;rsquo;s now intercept incoming requests and extract the CorrelationId value from the headers. In microservice architecture, applications communicate between services not only via HTTP but also through event-based methods. However, in such initiated requests, we will carry the CorrelationId value in the headers provided by MassTransit, not in the HTTP headers.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/correlationid-tracing-net-microservices-with-http-and-rabbitmq/filters-for-correlationid.png" alt="Filters for CorrelationId" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Filters for CorrelationId&lt;/p&gt;
&lt;h1 id="correlationmiddleware"&gt;CorrelationMiddleware&lt;/h1&gt;
&lt;p&gt;We define our CorrelationMiddleware class, which will intercept incoming HTTP requests and get the value from the CorrelationId HTTP header, as follows. If there is a value in the header, we first enrich it by putting this value as a separate field in the &lt;code&gt;LogContext&lt;/code&gt; class provided by Serilog. Then, we store the CorrelationId value by giving the Correlation class to our &lt;code&gt;AsyncStorage&lt;/code&gt; class. In this way, we will be able to get the CorrelationId value in incoming requests and use this value in outgoing requests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// When the Http request is made, it takes the CorrelationId value from the HttpContext Header and sets the Correlation.Id value.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationMiddleware&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; RequestDelegate _next;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; CorrelationMiddleware(RequestDelegate next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _next = next;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task InvokeAsync(HttpContext context, Correlation correlation)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlationIdHeader = context.Request.Headers[&lt;span style="color:#e6db74"&gt;&amp;#34;CorrelationId&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(correlationIdHeader))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlationId = Guid.Parse(correlationIdHeader.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LogContext.PushProperty(&lt;span style="color:#e6db74"&gt;&amp;#34;CorrelationId&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ScalarValue(correlationId));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AsyncStorage&amp;lt;Correlation&amp;gt;.Store(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Correlation
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = correlationId
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _next(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To add this middleware to our application, we edit Program.cs as follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseMiddleware&amp;lt;CorrelationMiddleware&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="correlationconsumefilter"&gt;CorrelationConsumeFilter&lt;/h1&gt;
&lt;p&gt;If the request coming to our service is not HTTP, but a request triggered by an event, there will not be an HTTP header. For this reason, we define our CorrelationConsumeFilter class as follows to get the CorrelationId value from the header values ​​provided by MassTransit. In this class, before consuming the classes signed with IConsumer, we intercept the incoming events and get the CorrelationId value and add it to &lt;code&gt;LogContext&lt;/code&gt; and &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// It is triggered when there is an event consumed by MassTransit and sets the CorrelationId value to the Correlation class.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationConsumeFilter&lt;/span&gt;&amp;lt;T&amp;gt; : IFilter&amp;lt;ConsumeContext&amp;lt;T&amp;gt;&amp;gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; T : &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Task Send(ConsumeContext&amp;lt;T&amp;gt; context, IPipe&amp;lt;ConsumeContext&amp;lt;T&amp;gt;&amp;gt; next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlationIdHeader = context.CorrelationId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlationIdHeader.HasValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlationId = correlationIdHeader.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LogContext.PushProperty(&lt;span style="color:#e6db74"&gt;&amp;#34;CorrelationId&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ScalarValue(correlationId));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AsyncStorage&amp;lt;Correlation&amp;gt;.Store(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Correlation
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = correlationId
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; next.Send(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Probe(ProbeContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s examine what we can do to add the Correlation value, which we store in &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt;, to the relevant header fields in the event requests.&lt;/p&gt;
&lt;h1 id="correlationpublishfilter"&gt;CorrelationPublishFilter&lt;/h1&gt;
&lt;p&gt;We define our &lt;code&gt;CorrelationPublishFilter&lt;/code&gt; class, which will intercept events published with MassTransit&amp;rsquo;s &lt;code&gt;IPublishEndpoint&lt;/code&gt;, as follows. This class intercepts published events, retrieves the CorrelationId value from &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt;, and adds it to MassTransit&amp;rsquo;s CorrelationId header.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Sets the CorrelationId value of events published via MassTransit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationPublishFilter&lt;/span&gt;&amp;lt;T&amp;gt; : IFilter&amp;lt;PublishContext&amp;lt;T&amp;gt;&amp;gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; T : &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Task Send(PublishContext&amp;lt;T&amp;gt; context, IPipe&amp;lt;PublishContext&amp;lt;T&amp;gt;&amp;gt; next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlation = AsyncStorage&amp;lt;Correlation&amp;gt;.Retrieve();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlation &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.CorrelationId = Guid.Parse(correlation.Id.ToString()!);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; next.Send(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Probe(ProbeContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="correlationsendfilter"&gt;CorrelationSendFilter&lt;/h1&gt;
&lt;p&gt;If the event sent to MassTransit is not published and is sent using &lt;code&gt;IRequestClient&amp;lt;T&amp;gt;&lt;/code&gt; with the request/response pattern, our &lt;code&gt;CorrelationSendFilter&lt;/code&gt; class will intervene, not &lt;code&gt;CorrelationPublishFilter&lt;/code&gt;. In this class, we intervene in the sent events, get the CorrelationId value from &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; and add it to the CorrelationId header value of MassTransit.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Sets the CorrelationId value of events sent via MassTransit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationSendFilter&lt;/span&gt;&amp;lt;T&amp;gt; : IFilter&amp;lt;SendContext&amp;lt;T&amp;gt;&amp;gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; T : &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Task Send(SendContext&amp;lt;T&amp;gt; context, IPipe&amp;lt;SendContext&amp;lt;T&amp;gt;&amp;gt; next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlation = AsyncStorage&amp;lt;Correlation&amp;gt;.Retrieve();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlation &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.CorrelationId = Guid.Parse(correlation.Id.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; next.Send(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Probe(ProbeContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To add our &lt;code&gt;CorrelationConsumeFilter&lt;/code&gt;, &lt;code&gt;CorrelationPublishFilter&lt;/code&gt; and &lt;code&gt;CorrelationSendFilter&lt;/code&gt; classes to our application, we define our ConfigureServices class as follows and use this method in Program.cs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ConfigureServices&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; WebApplicationBuilder AddMassTransit(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; WebApplicationBuilder builder, IConfiguration configuration)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; messageBroker = builder.Configuration.GetSection(&lt;span style="color:#e6db74"&gt;&amp;#34;MessageBroker&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.Services.AddMassTransit(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg.SetKebabCaseEndpointNameFormatter();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg.AddConsumers(Assembly.GetExecutingAssembly());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg.UsingRabbitMq((context, config) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.UseSendFilter(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(CorrelationSendFilter&amp;lt;&amp;gt;), context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.UsePublishFilter(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(CorrelationPublishFilter&amp;lt;&amp;gt;), context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.UseConsumeFilter(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(CorrelationConsumeFilter&amp;lt;&amp;gt;), context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.Host(messageBroker[&lt;span style="color:#e6db74"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;], messageBroker[&lt;span style="color:#e6db74"&gt;&amp;#34;VirtualHost&amp;#34;&lt;/span&gt;], h =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; h.Username(messageBroker[&lt;span style="color:#e6db74"&gt;&amp;#34;Username&amp;#34;&lt;/span&gt;]!);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; h.Password(messageBroker[&lt;span style="color:#e6db74"&gt;&amp;#34;Password&amp;#34;&lt;/span&gt;]!);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.ConfigureEndpoints(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; builder;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.AddMassTransit(builder.Configuration);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="correlationheaderhandler"&gt;CorrelationHeaderHandler&lt;/h1&gt;
&lt;p&gt;If the request made from our service is an HTTP request, not an event, we define our &lt;code&gt;CorrelationHeaderHandler&lt;/code&gt; class, which will intervene in the requests we make from our HttpClient class, which we will create with &lt;code&gt;IHttpClientFactory&lt;/code&gt;, as follows. In this class, we intervene in HTTP requests made from our application, get the CorrelationId value from &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; and add it as a HTTP header.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Middleware to be used in requests made with HttpClient. Adds the CorrelationId header to the requests made.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationHeaderHandler&lt;/span&gt; : DelegatingHandler
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlation = AsyncStorage&amp;lt;Correlation&amp;gt;.Retrieve();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlation &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request.Headers.Add(&lt;span style="color:#e6db74"&gt;&amp;#34;CorrelationId&amp;#34;&lt;/span&gt;, correlation.Id.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;base&lt;/span&gt;.SendAsync(request, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To add this middleware to our application, we define Named Http Client in Progam.cs as follows and add the middleware that will intervene in requests made with &lt;code&gt;AddHttpMessageHandler&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;CorrelationHeaderHandler&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHttpClient(&lt;span style="color:#e6db74"&gt;&amp;#34;Inventory&amp;#34;&lt;/span&gt;, c =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; c.BaseAddress = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5053&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddHttpMessageHandler&amp;lt;CorrelationHeaderHandler&amp;gt;();;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We use the Named Http Client we created as follows. If the requests we make here are not asynchronous, the middleware cannot intervene.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InventoryService&lt;/span&gt; : IInventoryService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IHttpClientFactory _httpClientFactory;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; InventoryService(IHttpClientFactory httpClientFactory)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _httpClientFactory = httpClientFactory;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task RemoveStockAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; productId, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; quantity)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; httpClient = _httpClientFactory.CreateClient(&lt;span style="color:#e6db74"&gt;&amp;#34;Inventory&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; httpClient.PostAsync(&lt;span style="color:#e6db74"&gt;$&amp;#34;remove-stock/{productId}/{quantity}&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response.EnsureSuccessStatusCode();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this article, we explored how to effectively use CorrelationId to enhance distributed traceability in a .NET microservice architecture using HTTP and RabbitMQ. By leveraging Middleware and Filter structures, we were able to manipulate header values and propagate the CorrelationId across different services, ensuring comprehensive logging and monitoring with Serilog&amp;rsquo;s LogContext framework.&lt;/p&gt;
&lt;p&gt;We demonstrated how to handle CorrelationId in both HTTP requests and event-driven communication facilitated by MassTransit. By implementing &lt;code&gt;CorrelationMiddleware&lt;/code&gt; and &lt;code&gt;CorrelationHeaderHandler&lt;/code&gt; for HTTP; &lt;code&gt;CorrelationConsumeFilter&lt;/code&gt;, &lt;code&gt;CorrelationPublishFilter&lt;/code&gt;, and &lt;code&gt;CorrelationSendFilter&lt;/code&gt; for event-driven communication. We ensured that the CorrelationId is consistently carried through the entire lifecycle of a request, providing a clear trace of the process flow and helping to identify performance or flow issues.&lt;/p&gt;
&lt;p&gt;The use of &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; with the &lt;code&gt;AsyncLocal&lt;/code&gt; class provided a reliable way to store and retrieve the CorrelationId value across different scopes, addressing challenges related to Dependency Injection and asynchronous processing.&lt;/p&gt;
&lt;p&gt;I hope this guide helps you implement a robust distributed tracing solution in your own microservice architecture. For more detailed examples, you can check out the complete code repository on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-correlation-id-microservices"&gt;GitHub - berkslv/lecture-correlation-id-microservices&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="resources"&gt;Resources&lt;/h1&gt;
&lt;p&gt;The source I used for the correct use of the AsyncLocal class:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://medium.com/@mbearz/how-to-log-everything-using-middleware-and-httpclient-handler-42b8f628fe84"&gt;https://medium.com/@mbearz/how-to-log-everything-using-middleware-and-httpclient-handler-42b8f628fe84&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>API Gateway Alternatives from a .NET Developer's Perspective: YARP, Ocelot, Kong, APISIX, and KrakenD</title><link>https://berkselvi.dev/posts/api-gateway-alternatives-from-a-net-developer-standpoint/</link><pubDate>Mon, 20 May 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/api-gateway-alternatives-from-a-net-developer-standpoint/</guid><description>&lt;p&gt;We can use API Gateway services as an intermediary layer while exposing our services to the outside world in microservice architecture. In this article, we will briefly discuss what an API Gateway is and then talk about .NET, OpenResty, and Go-based API Gateway alternatives along with performance tests.&lt;/p&gt;
&lt;h2 id="what-is-an-api-gateway"&gt;What is an API Gateway?&lt;/h2&gt;
&lt;p&gt;An API Gateway provides an entry point to our systems as shown below, isolating them from the outside world. It solves common issues such as authorization, logging, and rate limiting with a single service that would otherwise need to be implemented repeatedly in each sub-service.&lt;/p&gt;</description><content>&lt;p&gt;We can use API Gateway services as an intermediary layer while exposing our services to the outside world in microservice architecture. In this article, we will briefly discuss what an API Gateway is and then talk about .NET, OpenResty, and Go-based API Gateway alternatives along with performance tests.&lt;/p&gt;
&lt;h2 id="what-is-an-api-gateway"&gt;What is an API Gateway?&lt;/h2&gt;
&lt;p&gt;An API Gateway provides an entry point to our systems as shown below, isolating them from the outside world. It solves common issues such as authorization, logging, and rate limiting with a single service that would otherwise need to be implemented repeatedly in each sub-service.&lt;/p&gt;
&lt;p&gt;They can apply transformations to URLs, body information, and even protocols while routing requests. For example, an HTTP request can be transmitted to sub-services using the gRPC protocol for better performance. They can use flat files or databases to find the addresses of the services they will route incoming requests to.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/what-is-api-gateway.png" alt="What is API Gateway" loading="lazy" /&gt;
&lt;br/&gt;
&lt;h2 id="what-are-the-alternatives"&gt;What are the alternatives?&lt;/h2&gt;
&lt;p&gt;When evaluating API Gateway alternatives that we can use in our systems, we need to consider the required customization, performance, suitability for distributed architecture, and the ability to update route information via an admin panel. Unfortunately, there is no single gateway product that meets all these features and solves all our needs at once. We need to make these evaluations based on the specific needs of our system.&lt;/p&gt;
&lt;p&gt;First, we will examine the .NET-based YARP and Ocelot API gateway products, which can be customized with C#, then talk about Kong and Apache APISIX, which use the OpenResty (nginx and lua) combination. Finally, we will look at KrakenD, which is frequently used in cloud-native environments and developed with Go. All these options can be used open source for free, but we can get enterprise support for Kong, Apache APISIX, and KrakenD if needed.&lt;/p&gt;
&lt;p&gt;Before evaluating our options, I developed a very simple .NET API service that simulates database queries by waiting for 1 second. After putting this service behind the API Gateway, we will try to route requests and perform performance tests using the Apache Benchmark tool. To examine resource consumption more closely, we will run this application with other gateways using docker compose and receive responses from the service at service:8080.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddEndpointsApiExplorer();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSwaggerGen();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (app.Environment.IsDevelopment())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.UseSwagger();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.UseSwaggerUI();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGet(&lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;, () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task.Delay(&lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;).Wait();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Weather is OK!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.WithName(&lt;span style="color:#e6db74"&gt;&amp;#34;GetWeather&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.WithOpenApi();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="net-based"&gt;.NET Based&lt;/h2&gt;
&lt;p&gt;As .NET developers, we can start by examining the YARP and Ocelot API Gateway products that can be developed with .NET technologies, which usually come to mind first. While these options can compete with other competitors in very simple scenarios in terms of performance, they cannot respond to complex needs such as minimal resource consumption and admin dashboard.&lt;/p&gt;
&lt;h3 id="yarp"&gt;YARP&lt;/h3&gt;
&lt;p&gt;Microsoft&amp;rsquo;s YARP (Yet Another Reverse Proxy) stands out as a reverse proxy product and can also be used as an API Gateway. It caught my attention because it is used as an API Gateway in the eShop example microservice projects published by Microsoft on &lt;a href="https://github.com/dotnet/eShop"&gt;GitHub&lt;/a&gt;. YARP provides load balancing support and can route requests to services using algorithms like round robin by obtaining the addresses of these services through service discovery methods. Since it is .NET-based, processes such as authorization, rate limiting, distributed tracing, and logging that can be used in standard .NET API projects can also be easily implemented with familiar methods. Additionally, customizations can be made by adding middleware, and HTTP requests can be converted to gRPC requests.&lt;/p&gt;
&lt;p&gt;To create a YARP project, after creating a .NET web API project, you include the Yarp.ReverseProxy package in the application and configure it to start routing requests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddReverseProxy()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .LoadFromConfig(builder.Configuration.GetSection(&lt;span style="color:#e6db74"&gt;&amp;#34;ReverseProxy&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapReverseProxy();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;ReverseProxy&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;weather_route&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;ClusterId&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;weather_api&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Match&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Path&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Clusters&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;weather_api&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Destinations&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;destination1&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Address&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://service:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Logging&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;LogLevel&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Default&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Information&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Microsoft.AspNetCore&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Warning&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AllowedHosts&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="ocelot"&gt;Ocelot&lt;/h3&gt;
&lt;p&gt;Ocelot is an API Gateway solution that shares many common features with YARP. All features that can be used in .NET API projects can also be used here. It is developed as an independent open source project. Unlike YARP, it positions itself directly as an API Gateway product. However, it does not have gRPC support but offers features such as request aggregation, which can be preferred in backend-for-frontend structures.&lt;/p&gt;
&lt;p&gt;To create an Ocelot application, similarly, a .NET API project is created and the configurations in the Program.cs file are set up to read service configurations from a separate ocelot.json file instead of appsettings.json. When defining routes with Ocelot, we need to repeatedly provide the service addresses, whereas with YARP, we only need to give the service information in one place and specify it in the router. Request aggregation can be done in the aggregates section after defining routes, and for more complex needs, custom aggregates can be defined using the IDefinedAggregator interface.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Ocelot.DependencyInjection;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Ocelot.Middleware;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Configuration.AddJsonFile(&lt;span style="color:#e6db74"&gt;&amp;#34;ocelot.json&amp;#34;&lt;/span&gt;, optional: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, reloadOnChange: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddOcelot(builder.Configuration);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.UseOcelot();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.RunAsync();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ocelot.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;GlobalConfiguration&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;BaseUrl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://service:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="yarp-vs-ocelot"&gt;YARP vs Ocelot&lt;/h3&gt;
&lt;p&gt;As you can see in the comparison table I prepared for YARP and Ocelot technologies, they share most features. Our preference between these two technologies can depend on the supported features, community support, and the structure of the configuration file.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;YARP&lt;/th&gt;
&lt;th&gt;Ocelot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Routing &amp;amp; Request mapping&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authn &amp;amp; Authz&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load balancing&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No, old packages available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request aggregation&lt;/td&gt;
&lt;td&gt;No, custom development required&lt;/td&gt;
&lt;td&gt;Yes, can be done with configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distributed tracing&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On a Mac M1 with 8GB of RAM, running in a Docker environment with a very simple scenario where there is only one endpoint, I sent 10,000 requests with 50 concurrent channels using the Apache Benchmark tool for our performance tests. Ocelot returned an average response time of 1030 ms, and YARP returned an average response time of 1029 ms, yielding almost the same result. The memory consumption started at 20MB and rose to a maximum of 100MB when requests started coming in, and the CPU usage varied between 10% and 20%. These rates are almost the same for YARP and Ocelot. The commands I used for the tests are as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# YARP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 100 MB RAM | 10% - 20% CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:9090/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software: Kestrel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 207.561 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;1470000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;140000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.18 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1037.804 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.756 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 6.92 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1029&lt;/span&gt; 122.9 &lt;span style="color:#ae81ff"&gt;1015&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2961&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1028&lt;/span&gt; 122.9 &lt;span style="color:#ae81ff"&gt;1015&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2961&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1029&lt;/span&gt; 123.0 &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2962&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1019&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1021&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1028&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1033&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1052&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1517&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;2962&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Ocelot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 100 MB RAM | 10% - 20% CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:9090/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software: Kestrel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 207.739 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;1470000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;140000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.14 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1038.695 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.774 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 6.91 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;1001&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1030&lt;/span&gt; 120.0 &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3347&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;1001&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1030&lt;/span&gt; 119.9 &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3347&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;1001&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1030&lt;/span&gt; 120.0 &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3349&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1020&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1025&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1032&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1040&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1061&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1359&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;3349&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="openresty-based"&gt;OpenResty Based&lt;/h2&gt;
&lt;p&gt;Using nginx, which is known for its high performance, along with LuaJIT provides a flexible development environment with the Lua scripting language and JIT support, which supports hot reload without needing to restart. The Lua language, in many scenarios, offers performance as high as C due to its C API while providing higher-level programming with a more developer-friendly structure. It is possible to call Lua code from C code or vice versa. This results in a nice pair that can be used together. In fact, the C and Lua combination is a stack used in Cloudflare&amp;rsquo;s own tools and is frequently used in networking applications.&lt;/p&gt;
&lt;p&gt;We will talk about two OpenResty-based gateways that are frequently preferred in the industry: Kong, which has made a name for itself, and Apache APISIX, which has succeeded in distinguishing itself with a few nice features. We will continue with Go-based gateways afterwards.&lt;/p&gt;
&lt;h3 id="kong"&gt;Kong&lt;/h3&gt;
&lt;p&gt;Kong stands out among API gateway solutions. In addition to containing all the features found in tools like Ocelot and YARP, it offers additional features with over 60 plugin supports. You can also develop your own plugins with Lua or other programming languages. The enterprise version provides extra features such as GraphQL support, request validation, body update, and secret manager.&lt;/p&gt;
&lt;p&gt;Kong includes most of the features expected from an API gateway, such as authorization, logging, request transformation, and rate limiting. However, additional development is required for some features, such as the request aggregation pattern (combining the response from multiple services with a single request). While Lua, which can be used natively, works better than other alternatives, we can also develop using Go, JavaScript, or Python with IPC (Inter-Process Communication) support, although with lower performance. Moreover, thanks to WebAssembly support, we can create high-performance plugins with familiar languages like Go and Rust without needing IPC methods.&lt;/p&gt;
&lt;p&gt;Unlike .NET-based gateways like Ocelot and YARP, Kong offers different deployment options. With &amp;ldquo;DB-less&amp;rdquo; mode, management can be done through configuration files. This mode is very useful in CI/CD-based deployment scenarios and prevents issues that may arise from state differences.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-config-deployment.png" alt="Kong config deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;Kong can also be configured using the traditional method by using a database. In this method, databases such as PostgreSQL, Cassandra, Redis, InfluxDB, and Kafka can be used. Route and service information can be modified via the admin API. However, this scenario creates an additional dependency on the database and is more vulnerable to attacks since the control plane and data plane work on the same instance.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-traditional-deployment.png" alt="Kong traditional deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;By applying the deployment scenario we can use with the Konnect service, we achieve a more secure structure by separating the data plane and control plane parts from each other. Konnect is offered as software as a service by Kong and does not require an additional database dependency for state management.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-konnect-deployment.png" alt="Kong konnect deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;If we do not want to use a product managed by Kong and want the entire process to be done within our internal network, we can deploy the control plane and database to a different location in our system where they can communicate with each other using the method called Hybrid mode, and use the control plane to control the data plane.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-hybrid-deployment.png" alt="Kong hybrid deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;For local development, you can work with PostgreSQL using the Docker Compose file provided by Kong and read from this file at the first run. If you are working in DB-less mode, you need to define all your routes in a configuration file. After defining the configuration as below, we start our gateway with the Docker Compose file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# a very minimal declarative config file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;_format_version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;_transform&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;weather-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http://host.docker.internal:8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;routes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;get-weather&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;paths&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/weather&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kong&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_DOCKER_TAG:-kong:latest}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;user&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_USER:-kong}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt;: &lt;span style="color:#75715e"&gt;*kong-env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_ADMIN_ACCESS_LOG&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/dev/stdout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_ADMIN_ERROR_LOG&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/dev/stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_PROXY_LISTEN&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_PROXY_LISTEN:-0.0.0.0:8000}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_ADMIN_LISTEN&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_ADMIN_LISTEN:-0.0.0.0:8001}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_ADMIN_GUI_LISTEN&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_ADMIN_GUI_LISTEN:-0.0.0.0:8002}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_PROXY_ACCESS_LOG&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/dev/stdout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_PROXY_ERROR_LOG&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/dev/stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_PREFIX&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${KONG_PREFIX:-/var/run/kong}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_DECLARATIVE_CONFIG&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/opt/kong/kong.yaml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secrets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kong_postgres_password&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kong-net&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# The following two environment variables default to an insecure value (0.0.0.0)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# according to the CIS Security test.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_INBOUND_PROXY_LISTEN:-0.0.0.0}:8000:8000/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_INBOUND_SSL_PROXY_LISTEN:-0.0.0.0}:8443:8443/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Making them mandatory but undefined, like so would be backwards-breaking:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - &amp;#34;${KONG_INBOUND_PROXY_LISTEN?Missing inbound proxy host}:8000:8000/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - &amp;#34;${KONG_INBOUND_SSL_PROXY_LISTEN?Missing inbound proxy ssl host}:8443:8443/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Alternative is deactivating check 5.13 in the security bench, if we consider Kong&amp;#39;s own config to be enough security here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:8001:8001/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:8444:8444/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:8002:8002/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;healthcheck&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;test&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;kong&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;health&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;interval&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;retries&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;-&lt;span style="color:#ae81ff"&gt;failure:5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;read_only&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kong_prefix_vol:${KONG_PREFIX:-/var/run/kong}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kong_tmp_vol:/tmp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./config:/opt/kong&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;security_opt&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#66d9ef"&gt;no&lt;/span&gt;-&lt;span style="color:#ae81ff"&gt;new-privileges&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;KONG_DATABASE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;postgres docker compose --profile database up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After a successful deployment with Kong, requests made to the specified route and service information are routed. If you deploy using the traditional or hybrid mode, you can manage your service and endpoint information via an admin interface.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-admin-gui.png" alt="Kong admin gui" loading="lazy" /&gt;
&lt;br/&gt;
&lt;h3 id="apache-apisix"&gt;Apache APISIX&lt;/h3&gt;
&lt;p&gt;Apache APISIX is a gateway product developed on OpenResty like Kong. However, it stands out by offering features that are only available in the enterprise version of Kong as open source. These features include GraphQL, Canary release, and secret management.&lt;/p&gt;
&lt;p&gt;APISIX provides higher performance compared to Kong in systems with a high number of routes. While Kong uses a traversal search algorithm among routes, APISIX performs searches with the radixtree algorithm and provides 140% higher performance when no plugin is used. Additionally, APISIX uses the etcd database to store configurations. This database can run cloud-native and is widely used in Kubernetes environments.&lt;/p&gt;
&lt;p&gt;APISIX offers three different deployment scenarios:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Traditional Method&lt;/strong&gt;: Control plane and data plane are deployed within the same instance dependent on the database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decoupled Method&lt;/strong&gt;: Control plane and data plane are deployed with different instances, and only the control plane is dependent on the database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standalone Method&lt;/strong&gt;: Works by reading from a configuration file without using a database.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my local tests, I performed the deployment process using the traditional method with the etcd database through Docker Compose. I made minor changes to the Docker Compose file found in the APISIX Docker repository. We can make requests to the Admin API using admin keys. If I were to deploy using the standalone method, I would need to define routes in a yaml file.&lt;/p&gt;
&lt;p&gt;If enterprise support is required, it can be obtained through &lt;a href="http://api7.ai/"&gt;API7&lt;/a&gt;, managed by the APISIX development team.&lt;/p&gt;
&lt;p&gt;I will share the Docker Compose files and the code I used at the end of the blog post so you can quickly experience it on your own local environment.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-vs-apisix.png" alt="Kong vs APISIX" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;In performance tests, APISIX showed better performance than Kong with an average response time of 1025 milliseconds compared to Kong&amp;rsquo;s 1032 milliseconds. In terms of consumption, Kong consumes around 280MB of memory upon startup, while APISIX uses around 100MB. Both gateways have a CPU consumption rate that varies between 5% and 10%.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Kong&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 280MB RAM | 5% - 10% CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:8000/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software: Kestrel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;8000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 207.499 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;2730028&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;140000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.19 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1037.494 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.750 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 12.85 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.2 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1032&lt;/span&gt; 120.5 &lt;span style="color:#ae81ff"&gt;1018&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3369&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1031&lt;/span&gt; 120.5 &lt;span style="color:#ae81ff"&gt;1018&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3369&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1032&lt;/span&gt; 120.7 &lt;span style="color:#ae81ff"&gt;1019&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3373&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1019&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1026&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1028&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1034&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1043&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1066&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1326&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;3373&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# APISIX&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 100MB RAM | 5% - 10% CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:9080/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software: APISIX/3.9.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 206.130 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;1520000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;140000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.51 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1030.650 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.613 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 7.20 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.2 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;921&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1025&lt;/span&gt; 126.2 &lt;span style="color:#ae81ff"&gt;1012&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3376&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;921&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; 126.2 &lt;span style="color:#ae81ff"&gt;1011&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3375&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;921&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1025&lt;/span&gt; 126.3 &lt;span style="color:#ae81ff"&gt;1012&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3378&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1012&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1015&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1017&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1018&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1029&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1047&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1327&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;3378&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="go-based-alternatives"&gt;Go Based alternatives&lt;/h2&gt;
&lt;p&gt;As a Go-based API Gateway alternative, we will discuss KrakenD, the most prominent option. However, Tyk or Traefik API Gateway can also be preferred as secondary alternatives with Go, but they are not included in this narrative as I could not try them since they are paid.&lt;/p&gt;
&lt;h3 id="krakend"&gt;KrakenD&lt;/h3&gt;
&lt;p&gt;KrakenD is an API gateway product developed with Go and has both community and enterprise versions. It runs on the Lura engine, which was donated to the Linux Foundation in 2021. Compared to other API gateway products, it offers a powerful aggregation system. You can perform all aggregation operations using only configuration files without writing any code.&lt;/p&gt;
&lt;p&gt;Since KrakenD does not use a database, it does not create confusion when deciding on deployment options. We can perform our operations using only configuration files. However, it does not have an admin panel due to the lack of a database. The absence of a database eliminates additional error sources and provides easier scalability.&lt;/p&gt;
&lt;p&gt;All configurations are managed through a configuration file that can be tracked via version control systems such as Git. It supports different file formats like JSON, YAML, and TOML. We can quickly create configurations using the interface on KrakenD&amp;rsquo;s website. In a local development environment, you can quickly start and make requests to your service, as it only works through the configuration file. With the hot reload feature, you can update the configuration file with a short downtime. However, this method is not recommended for production environments; using GitOps methods to create a new release is advised. The enterprise version includes additional features such as OpenAPI importer and exporter, gzip compression, and response validation.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/krakend-designer.png" alt="KrakenD designer" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;KrakenD has a much simpler Docker Compose content compared to Kong and APISIX, as it does not have any additional dependencies. You only need to provide the path to the configuration file as a volume. For example, I added the weather service to Docker Compose and quickly started the service by creating the krakend.json file. It gave very good results in the performance test.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;$schema&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://www.krakend.io/schema/krakend.json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;KrakenD - API Gateway&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;timeout&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3000ms&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;cache_ttl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;300s&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;output_encoding&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;endpoints&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;endpoint&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;output_encoding&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;backend&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;url_pattern&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;encoding&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;sd&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;static&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;host&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://service:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;disable_host_sanitize&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;krakend&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;devopsfaith/krakend&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./krakend.json:/etc/krakend/krakend.json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;krakend-net&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;weather-service&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;../service/Service.API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;krakend-net&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;8080:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;krakend-net&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;external&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As KrakenD&amp;rsquo;s website also states, it works faster than Kong and APISIX. In similar test results, Kong responds with an average response time of 1032 milliseconds, APISIX with 1025 milliseconds, while KrakenD provides the best performance with 1024 milliseconds. The CPU consumption remains stable between 4% and 6%, with a maximum memory consumption of 30MB. These results show that KrakenD consumes fewer resources while being faster than its competitors.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# KrakenD&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 30MB RAM | 4% - 6% CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;ab -k -n 10000 -c 50 http://localhost:9090/weather&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Server Software&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Server Hostname&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;localhost&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Server Port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Document Path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/weather&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Document Length&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Concurrency Level&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Time taken for tests&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;206.359&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;seconds&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Complete requests&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Failed requests&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;(Connect&lt;/span&gt;: &lt;span style="color:#f92672"&gt;0, Receive&lt;/span&gt;: &lt;span style="color:#f92672"&gt;0, Length&lt;/span&gt;: &lt;span style="color:#f92672"&gt;9, Exceptions&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Non-2xx responses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Keep-Alive requests&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Total transferred&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2439352&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;HTML transferred&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;139874&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Requests per second&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;48.46&lt;/span&gt; [&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Time per request&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1031.795&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;ms] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Time per request&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;20.636&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;ms] (mean, across all concurrent requests)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Transfer rate&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;11.54&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;Kbytes/sec] received&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Connection Times (ms)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;min mean[+/-sd] median max&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Connect&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Processing&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;122.7&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1010&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3021&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Waiting&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;122.7&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1010&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3021&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Total&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;122.8&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1010&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Percentage of the requests served within a certain time (ms)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 1010&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;66&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 1012&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;75&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 1013&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 1015&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 1018&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;95&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;98&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 1037&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;99&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 1520&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;% 3023 (longest request)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;To compare the five API gateway products we examined, I prepared a table as shown below. This table summarizes the content of the presentation and I would like to emphasize a few important points based on my research.&lt;/p&gt;
&lt;h3 id="kong-and-apisix"&gt;&lt;strong&gt;Kong and APISIX&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If different teams are sharing a single API gateway to expose their projects to the internet and a live-updatable admin interface is needed, Kong or APISIX can be preferred. Both gateways have strong features and stand out with their extensible structures. Additionally, with the admin interface, it is possible to manage configurations and make updates live.&lt;/p&gt;
&lt;h3 id="krakend-1"&gt;&lt;strong&gt;KrakenD&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If the API gateway project is a shared responsibility among teams and developers can update the configuration file and run the pipeline, KrakenD can be a good choice in cases where high performance and low resource consumption are targeted. KrakenD, developed with Go, offers a powerful aggregation system and is easier to scale due to not using a database.&lt;/p&gt;
&lt;h3 id="ocelot-and-yarp"&gt;&lt;strong&gt;Ocelot and YARP&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If performance and resource consumption are not critical and a familiar technology is sufficient, we can continue with Ocelot or YARP, the .NET-based tools. These tools support similar features in different ways and are easy to integrate into the .NET ecosystem.&lt;/p&gt;
&lt;h3 id="other-criteria"&gt;&lt;strong&gt;Other Criteria&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deployment Methods&lt;/strong&gt;: The way the gateway is deployed, the structure and editability of the configuration files are important.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Community and Enterprise Support&lt;/strong&gt;: It is also important to consider how large the user base of the gateway is and whether there are sources of support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, there is no single product that offers a perfect solution to all our needs. Therefore, we should evaluate our own needs and decide based on the features supported by the tool we choose. Choosing the right API gateway is a critical step for the success of your project and it is always best to choose the one that best meets your needs.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;YARP&lt;/th&gt;
&lt;th&gt;Ocelot&lt;/th&gt;
&lt;th&gt;Kong&lt;/th&gt;
&lt;th&gt;APISIX&lt;/th&gt;
&lt;th&gt;KrakenD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request aggregation&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;td&gt;Simple, can be done with configuration&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Powerful,, can be done with configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAuth &amp;amp; OpenID&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request validation&lt;/td&gt;
&lt;td&gt;Needs custom logic&lt;/td&gt;
&lt;td&gt;Needs custom logic&lt;/td&gt;
&lt;td&gt;Comes with enterprise&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP to gRPC&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Websocket&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Comes with enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;File, PostgreSQL, Cassandra, Redis, InfluxDB, Kafka&lt;/td&gt;
&lt;td&gt;File, etcd&lt;/td&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Native&lt;/strong&gt;: Lua &amp;amp; &lt;strong&gt;IPC&lt;/strong&gt;: Go, Javascript, Python &amp;amp; &lt;strong&gt;WASM&lt;/strong&gt;: Go, Rust vs.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Native&lt;/strong&gt;: Lua &amp;amp; &lt;strong&gt;IPC&lt;/strong&gt;: Go, Javascript, Python &amp;amp; &lt;strong&gt;WASM&lt;/strong&gt;: Go, Rust vs.&lt;/td&gt;
&lt;td&gt;Go &amp;amp; Lua&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1029ms, &lt;strong&gt;Memory&lt;/strong&gt;: 100MB, &lt;strong&gt;CPU&lt;/strong&gt;: %10 - %20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1030ms, &lt;strong&gt;Memory&lt;/strong&gt;: 100MB, &lt;strong&gt;CPU&lt;/strong&gt;: %10 - %20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1035ms, &lt;strong&gt;Memory&lt;/strong&gt;: 280MB, &lt;strong&gt;CPU&lt;/strong&gt;: %5 - %10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1025ms, &lt;strong&gt;Memory&lt;/strong&gt;: 100MB &lt;strong&gt;CPU&lt;/strong&gt;: %5 - %10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1024ms, &lt;strong&gt;Memory&lt;/strong&gt;: 30MB, &lt;strong&gt;CPU&lt;/strong&gt;: %4 - %6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you want to access the source code, you can find the whole project on my GitHub account:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-api-gateway-comparison"&gt;GitHub - berkslv/lecture-api-gateway-comparison&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion-1"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>How to Use RSA for Encryption in JavaScript and Decryption in .NET</title><link>https://berkselvi.dev/posts/how-to-use-rsa-for-encryption-in-javascript-and-decryption-in-net/</link><pubDate>Thu, 07 Mar 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/how-to-use-rsa-for-encryption-in-javascript-and-decryption-in-net/</guid><description>&lt;p&gt;In the digital world, securing communication between different systems is paramount. RSA encryption is an asymmetric encryption algorithm and provides a robust way to achieve this by using a pair of keys: one for encryption and one for decryption. On the other hand, symmetric encryption algorithms, such as AES, use a single key for encryption and decryption. For these reasons, asymmetric encryption algorithms may be preferred in order to use the key publicly and avoid problems even if it falls into the hands of an attacker.&lt;/p&gt;</description><content>&lt;p&gt;In the digital world, securing communication between different systems is paramount. RSA encryption is an asymmetric encryption algorithm and provides a robust way to achieve this by using a pair of keys: one for encryption and one for decryption. On the other hand, symmetric encryption algorithms, such as AES, use a single key for encryption and decryption. For these reasons, asymmetric encryption algorithms may be preferred in order to use the key publicly and avoid problems even if it falls into the hands of an attacker.&lt;/p&gt;
&lt;p&gt;In this blog post I will guide you through implementing RSA key generation with OpenSSL, encryption in JavaScript and decryption in .NET, ensuring secure data exchange between a frontend application and a backend service.&lt;/p&gt;
&lt;h2 id="understanding-rsa-encryption"&gt;Understanding RSA Encryption&lt;/h2&gt;
&lt;p&gt;RSA (Rivest-Shamir-Adleman) is one of the first public-key cryptosystems and is widely used for secure data transmission. It involves two keys: a public key, which can be shared openly, for encrypting messages, and a private key, must be kept secret, for decrypting them. The strength of RSA lies in the difficulty of factoring the product of two large prime numbers, which underpins its security.&lt;/p&gt;
&lt;h2 id="generation-of-publicprivate-key-pairs-with-openssl"&gt;Generation of Public/Private key pairs with OpenSSL&lt;/h2&gt;
&lt;p&gt;One of the foundational steps in implementing RSA encryption and decryption is the generation of public and private key pairs. OpenSSL, a robust open-source tool for cryptography, offers a straightforward way to generate these keys. However, this step is optional. .NET libraries successfully generate RSA keys. However, using open source tools like this allows us to be ensure of the standards. This section will guide you through the process of generating an RSA public/private key pair using OpenSSL.&lt;/p&gt;
&lt;p&gt;We can start with generation of private key as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ openssl genrsa -out privatekey.pem &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cat privatekey.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----BEGIN PRIVATE KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDuOitcIPd5UpGN
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/4GEwNhc4fAmGP6fREJJk/byRgiLiWcyXwdoPwXdXjKSjkSIcETdz/hLAlb6+zNY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Gi4Ap0S5flZSRiKh60xWRA4vZVgsLqhEi2IsclSlWu3R7KD5dkBGsyf5xlCfNSs4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DbYYMgrXXZxXQ0c4qCikpnEB6OpqSzm19Cwrdq9GwophoAPxnf00d7S+y5QpRa+l
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Ca70xQ42FxBpMuK76a1g6i+JtmPgTYqXZ0FCMnUEzBq+u3N8LKoNzcSNdrLgvmHQ
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oOLFA/9BPe34YgLxoCUi+HhEiLSnvgz/Kn1yprvsz37baYizU/YWFG8s80SPmFsY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;JARxvZiJAgMBAAECggEAEboc42Jln+7Lu34NmIAUKZc7fE5EVjwpVZnP2Lfq510+
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Y2JsZe64pEAf8cVp5qA6E6pn3scKC2uZZr8t+Kj5xXbX/A/RgGyGte2jA5ZeQQ1o
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;wo7/q3CHiXVyeHpMATwyLMCkoyLFeY9mi6mDiRniMiP6YAj22gmzWoszIhH/rZCO
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NjdpWRsIkFp8jIiyRhhoaBoJtZ1qV3twk8HH590rgjS7QUGJWK9uUsi5SDh69MsR
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;r/SJ00gTMYFozFd6dB29aKj/p8fSk6qzh2noxU37AuiVc1A2H8Z6SAfRUDSvPkxA
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FdqVr62iLWmypBX2VYQ2p1pH1N0v8oG7NTxtrxgRTQKBgQD3YdxKNT0dWZJRGPFC
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DIRZVn86UgDAHRw29aToLARnepE5XOrLSmlGMqbK8vzRyUnlctJyG7x5ure2BMp4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gKSLuhz48n7ZvpHyCVnNsqqkc21iM/whL/yKiI11Jz5koXmRDltz9IioQH/PwXvD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RJbjxISHjG5YEKlVmztWtTup/QKBgQD2hqouE8JLePAUzK3SlfgVTlmaHDUeJev4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;u95RWPnMNOOwLUWbGYiKBoJ8uc2nSHmxIvEvEPGNwafR6etf0YocpwDfF5pnPDjk
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yA4hOdZrCyYa/UDqkB5pHTOZj5iVct1hBNbiANz6kL7Pl+H221U+HxD9rhaDPDez
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gdYPhLF4fQKBgQCdi2QomlkufOHS5eiXoLMU1iI2eQzjTGawlaYM+iNf503fU05w
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;YxZCT3WroC3kSKXYC1T8uK9CcugWclyje4HPPpq+7GhesZ/unYfkmRlVm/EYbnTu
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;icnyS96Ssohou/FYsOULJrt1M4ZyQA1aoS7bJUclsAiB6zJ8Q1z57ndt3QKBgEwa
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;9p6S6wBxrWw+Y8sHETdCoNa6rotIGbkIBnIGjddE3KWe1EY1c7lomQ/4LEzgSvEs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;YFivWmLwzeY32LoT7hc6V0KH/tqv9MBsIjCPsmoJXxIl7Mx9AWZh5XQaqHg6pa01
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2UCcE5wm40EjGlcjDwXGgXBPNhP9mxSHmJXh2QfhAoGANouUrF3qv8ofb1HGrUXx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DO/WkGMzwbkNmwwdgcGYVsGr3Dv/zx1y3TJEvqNh0ViVgmfdYveCo3VrSawq890U
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GJZXld2KFE1icEJpNoURkM9okZMzTDIMs+r4vq5Or2jB04mMuoCbG6rjfqUMtkll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oAIFOSKH+F3TrXjNgy4/juo&lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----END PRIVATE KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After we have successfully created the private key, we can create the public key with the private key we created.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ openssl rsa -in privatekey.pem -outform PEM -pubout -out publickey.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cat privatekey.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----BEGIN PUBLIC KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7jorXCD3eVKRjf+BhMDY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;XOHwJhj+n0RCSZP28kYIi4lnMl8HaD8F3V4yko5EiHBE3c/4SwJW+vszWBouAKdE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uX5WUkYioetMVkQOL2VYLC6oRItiLHJUpVrt0eyg+XZARrMn+cZQnzUrOA22GDIK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;112cV0NHOKgopKZxAejqaks5tfQsK3avRsKKYaAD8Z39NHe0vsuUKUWvpQmu9MUO
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NhcQaTLiu+mtYOovibZj4E2Kl2dBQjJ1BMwavrtzfCyqDc3EjXay4L5h0KDixQP/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QT3t+GIC8aAlIvh4RIi0p74M/yp9cqa77M9+22mIs1P2FhRvLPNEj5hbGCQEcb2Y
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;iQIDAQAB
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----END PUBLIC KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="setting-up-the-environment-for-net-rsa-decryption"&gt;Setting Up the Environment for .NET RSA Decryption&lt;/h2&gt;
&lt;p&gt;For .NET decryption I will use .NET 8 and use the System.Security.Cryptography namespace. We don’t need any other nuget package for RSA decryption. For RSA Decryption I created a Helper class like this.&lt;/p&gt;
&lt;p&gt;We read RsaKey from appsettings.json. You should add \n for newlines in RsaKey field. We put this key in appsettings.json because we want to use same keys in every request. But If you leave it to the responsibility of the .NET library, in every startup there will be newly generated key.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Logging&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;LogLevel&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Default&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Information&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Microsoft.AspNetCore&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Warning&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AllowedHosts&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;RsaKey&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuan+y++Eqc2Vx3QIt9gv0r6rzfcxpsBRubcWCmI+8tqb40oflv5dViPSiCSWNgg5xKk7K8WTeEQQ6NDg1IJ2OwoQ2dfzat5qlpfV9EeF3u8iTY/hyQaaYFwB77cV9t5Czb8oG/+IIOByorJVds9tAoKjssKUZ3W9IU7ffElZjZrbjoiy/H4z8u9fOq8IL9Zf3pHgzv2FxF4BPJamqr4s1VtMqGJ5g18wV1OD9gcz9pJOHHUVieZ0+xP4WD4+1wCv/uwEgIcqmEs0os6birHZL1X/CSqBSPc8e/+kkZyyzoF+MBWPzvAwmW32alIxz2ZV0Z+jJtoOrh/qVqbrGAf+RAgMBAAECggEAEWYu4IJHg0ZZOZtgRwj7RtjKXzluraFi3GRHdoB1IFCBRiOsamMrO91qeAqdDCmL+saLbyvXEd8VMqA4djZPeWkWqt8ozwHPY9RzMZuZyCm7t9Zad71sWtI6mmJNF/46qWfOudWHbSX51+rFiMAzMFaGm3wAsFyaaBbv6gkohIhVrrMvpIuV8X8JNI8/VNlOR6vExHd/3uWKi8vPrFEFTNQ1UE+WJDkVojcmx0t63jlP8C/O4ofaJeCYRkAaM9+FXnM+jVNf0qZU+3JGYmy1R/B7L+LyrbG2uYTaBa1Ba9zGsXD1HQ4dctK+MltBd4p+MWgHAQV+xuVmGGDkylpb8QKBgQDP9zY4UzLmnt0werJF9SGmAJU51Y/+gannUC/+XYqnK/NA5iUDR86rlYCJbqQCceyMYJSYuePumygWOVL9rHnu1/fTMG/sXnRktmTSFemgeCYfPyn8/KeZKn6E5xw8GbtfLJLA8uM4DakDf9vSpilKDYT4UbZyKgq7/yodPXbgCQKBgQDWs4gL4yOtDLAY/OQKZvoyXClapldkVFRzf97pBJQGLYfdTzkVFviWsgI/yrA+t5tlzaWtZieuv2KsSmazVyO2W5ei9HILx4yRFK7K56OM5o7wqjN62ZmF0R+422YNaAyqYAd9nhLjgdsrmZ9FTxhQ91asBSH1AO3j6xaZUvh1SQKBgAZuo/ur/xgI89hrAxaM1WSYAgWO6Gw7wHCKF2HrrL0s69InDCAE2YyPDDGz/ViiA2n4FsB+h2E65UuCrGFyMzdC8MRUbDHIXhs7VPT2fopbDPrMblUHz3s6SD1+FG57cUMpUsSq/oIeUgrsqnTidMZ4kpNHm7f+OuTDqJ7M5t9ZAoGAAfm2570YR/BU8nXpNztJVAtLCh17sl2gRUvI5kX3grMKi/u9n7cNZH2Qzbt0sa8Iy///ZUAKX249Xy50EXRczMG8/G/ZWMhmP7N8BDvrYlGAwTAftyKnafbJnu7N2pO5ghvOFdbNf7BjLtyD/aRDqgMMlhqZ/GIczjsMgy6jQJkCgYATxkjdW/mnWLDDZAYm1V10Gnm7YDMwX3gKdhpoYleeeapZiDlurkR3YAEvQezcnhv3NpjaKQtF1/3Q0fZJ0wBc5FflaevHRjHTa0IrU5QIa48dLDx+jxn+cUCFx9kirkb7JcfJLZWtToPMLfCZt324P2ogZgWBwweVPiz1/voEXw==\n-----END PRIVATE KEY-----&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Helpers/RsaEncryptionHelper.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RsaEncryptionHelper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; RSA _rsa = RSA.Create();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; RsaEncryptionHelper(IConfiguration configuration)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; privateKeyPem = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;RsaKey&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrEmpty(privateKeyPem)) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentNullException(nameof(privateKeyPem));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ImportKey(privateKeyPem);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ImportKey(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; privateKey)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _rsa.ImportFromPem(privateKey.ToCharArray());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetPublicKey()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; publicKey = _rsa.ExportSubjectPublicKeyInfoPem();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; publicKey;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetPrivateKey()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; privateKey = _rsa.ExportPkcs8PrivateKeyPem();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; privateKey;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Encrypt(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; dataBytes = Encoding.UTF8.GetBytes(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; encryptedData = _rsa.Encrypt(dataBytes, RSAEncryptionPadding.OaepSHA256);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Convert.ToBase64String(encryptedData);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Decrypt(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; dataBytes = Convert.FromBase64String(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; decryptedData = _rsa.Decrypt(dataBytes, RSAEncryptionPadding.OaepSHA256);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Encoding.UTF8.GetString(decryptedData);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And use this helper class inside of EncryptionController to use helper class’s methods. In production enviroments You should not be expose PrivateKey to anyone. This should be kept secret.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Controllers/EncryptionController.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;api/[controller]&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EncryptionController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; RsaEncryptionHelper _rsaEncryptionHelper;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; EncryptionController(RsaEncryptionHelper rsaEncryptionHelper)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _rsaEncryptionHelper = rsaEncryptionHelper;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpPost(&amp;#34;Encrypt&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Encrypt([FromBody] EncryptionRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _rsaEncryptionHelper.Encrypt(request.Data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpPost(&amp;#34;Decrypt&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Decrypt([FromBody] EncryptionRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _rsaEncryptionHelper.Decrypt(request.Data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;PublicKey&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; PublicKey()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _rsaEncryptionHelper.GetPublicKey();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;PrivateKey&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; PrivateKey()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _rsaEncryptionHelper.GetPrivateKey();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpPost(&amp;#34;ImportKey&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ImportKey([FromBody] ImportKeyRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _rsaEncryptionHelper.ImportKey(request.PrivateKey);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ImportKeyRequest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; PrivateKey { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;init&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;!;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EncryptionRequest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Data { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;init&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;!;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After this development we made in the backend side we can continue to JavaScript side. Thanks to all we’ve done we share our public key from endpoint with client, then clients could use this public key to encrypt their data for secure transmission between sides.&lt;/p&gt;
&lt;h2 id="setting-up-the-development-environment-for-javascript-rsa-encryption"&gt;Setting Up the Development environment for JavaScript RSA Encryption&lt;/h2&gt;
&lt;p&gt;Before diving into the code, ensure your environment is set up for JavaScript development. For this example, we’ll use the Web Crypto API, which is built into modern browsers and provides cryptographic operations including RSA encryption. Therefore you don’t need to download any npm packages.&lt;/p&gt;
&lt;p&gt;If I am going to develop a browser application with JavaScript, I usually start with Vite. To do this, we can start a project using vanilla js or React by entering the following command. I will develop a small React project for demonstration purposes, but it will be enough even if you stick with vanilla js.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm create vite@latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After that I will create a src/utils/RsaEncryptionHelper.js file and fill that file as follows. With this file’s methods you can encrypt and decrypt with RSA algorithm but usually decryption is a resposibility of backend side because decryption requires private key and javascript client’s should not be store or access private keys publicly because of security reasons. But I am sharing this in case anyone wants to try something similar to two-way key encryption like in the SSL.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// src/utils/RsaEncryptionHelper.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encryptAlgorithm&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;RSA-OAEP&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;modulusLength&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;publicExponent&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Uint8Array&lt;/span&gt;([&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;extractable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;hash&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SHA-256&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;arrayBufferToBase64&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;arrayBuffer&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteArray&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Uint8Array&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;arrayBuffer&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteString&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteArray&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;byteLength&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;byteString&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; String.&lt;span style="color:#a6e22e"&gt;fromCharCode&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;byteArray&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;b64&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; window.&lt;span style="color:#a6e22e"&gt;btoa&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;byteString&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;b64&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64StringToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b64str&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteStr&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;atob&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b64str&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Uint8Array&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;byteStr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteStr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteStr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;charCodeAt&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;buffer&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;convertPemToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;pem&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pem&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;split&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encoded&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;trim&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----BEGIN RSA PRIVATE KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----BEGIN PRIVATE KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----BEGIN PUBLIC KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----END RSA PRIVATE KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----END PRIVATE KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----END PUBLIC KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encoded&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;trim&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64StringToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;encoded&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encryptRsa&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;pemString&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// convert string into ArrayBuffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encodedPlaintext&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextEncoder&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;encode&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;buffer&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keyArrayBuffer&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;convertPemToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;pemString&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// import public key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;secretKey&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;crypto&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;subtle&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;importKey&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;spki&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keyArrayBuffer&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encryptAlgorithm&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;encrypt&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// encrypt the text with the public key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encrypted&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;crypto&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;subtle&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;encrypt&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;RSA-OAEP&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secretKey&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encodedPlaintext&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// store data into base64 string
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;arrayBufferToBase64&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;encrypted&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Encryption Error:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decryptRsa&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;pemString&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// convert base64 encoded input string into ArrayBuffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encodedPlaintext&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64StringToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keyArrayBuffer&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;convertPemToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;pemString&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// import private key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;secretKey&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;crypto&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;subtle&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;importKey&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;pkcs8&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keyArrayBuffer&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encryptAlgorithm&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;decrypt&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// decrypt the text with the public key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decrypted&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;crypto&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;subtle&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;decrypt&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;RSA-OAEP&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secretKey&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encodedPlaintext&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// decode the decrypted ArrayBuffer output
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;uint8Array&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Uint8Array&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;decrypted&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;textDecoder&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextDecoder&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decodedString&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;textDecoder&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;decode&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;uint8Array&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decodedString&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Decryption Error:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I developed a small React project using this helper methods. You can use your public and private keys that will be shared from .NET API to encrypt and decrypt data, but keep in mind decryption should be doing in backend side with private key that kept secret.&lt;/p&gt;
&lt;a href="https://lecture-rsa-dotnet-javascript.vercel.app" target="_blank"&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-rsa-for-encryption-in-javascript-and-decryption-in-net/rsa-encryption.webp" alt="RSA Encryption (lecture-rsa-dotnet-javascript.vercel.app)" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;RSA Encryption (lecture-rsa-dotnet-javascript.vercel.app)&lt;/p&gt;
&lt;/a&gt;
&lt;p&gt;If you want to access the source code, you can find the whole project on my GitHub account:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-rsa-dotnet-javascript"&gt;GitHub - berkslv/lecture-rsa-dotnet-javascript&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>How to use Ocelot and Keycloak together to secure Microservices from API Gateway</title><link>https://berkselvi.dev/posts/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/</link><pubDate>Tue, 30 Jan 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/</guid><description>&lt;p&gt;In the dynamic landscape of microservices architecture, the need for robust security has become much more important. As organizations break down their applications into smaller, independently deployable services, ensuring the integrity and confidentiality of data exchanged between these services becomes a critical concern.&lt;/p&gt;
&lt;p&gt;In this blog post, we will explore a comprehensive solution for securing microservices using Ocelot, an API Gateway with Keycloak, a powerful open-source identity and access management solution. By placing Keycloak behind the API Gateway, we’ll delve into how this integration safeguards resources, authenticates requests to other services, and authorizes access using claims, offering a seamless and secure communication framework for your microservices ecosystem. Let’s get started.&lt;/p&gt;</description><content>&lt;p&gt;In the dynamic landscape of microservices architecture, the need for robust security has become much more important. As organizations break down their applications into smaller, independently deployable services, ensuring the integrity and confidentiality of data exchanged between these services becomes a critical concern.&lt;/p&gt;
&lt;p&gt;In this blog post, we will explore a comprehensive solution for securing microservices using Ocelot, an API Gateway with Keycloak, a powerful open-source identity and access management solution. By placing Keycloak behind the API Gateway, we’ll delve into how this integration safeguards resources, authenticates requests to other services, and authorizes access using claims, offering a seamless and secure communication framework for your microservices ecosystem. Let’s get started.&lt;/p&gt;
&lt;h2 id="create-keycloak-instance"&gt;Create Keycloak instance&lt;/h2&gt;
&lt;p&gt;Running keycloak within Docker is very easy. Just place this Dockerfile in /Identity directory in your project. You can find all of the codes in my repository from end of the post.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;quay.io/keycloak/keycloak:latest&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#e6db74"&gt;builder&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Enable health and metrics support &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; KC_HEALTH_ENABLED&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; KC_METRICS_ENABLED&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Configure a database vendor &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; KC_DB&lt;span style="color:#f92672"&gt;=&lt;/span&gt;postgres
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/opt/keycloak&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# for demonstration purposes only, please make sure to use proper certificates in production instead &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt; -dname &lt;span style="color:#e6db74"&gt;&amp;#34;CN=server&amp;#34;&lt;/span&gt; -alias server -ext &lt;span style="color:#e6db74"&gt;&amp;#34;SAN:c=DNS:localhost,IP:127.0.0.1&amp;#34;&lt;/span&gt; -keystore conf/server.keystore &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; /opt/keycloak/bin/kc.sh build &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;quay.io/keycloak/keycloak:latest&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;builder /opt/keycloak/ /opt/keycloak/ &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# change these values to point to a running postgres instance &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENTRYPOINT&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;/opt/keycloak/bin/kc.sh&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And create docker-compose.yml file that access /Identity/Dockerfile and builds image from it. We can use base image rather than custom Dockerfile but with this approach you can customize much more features of Keycloak like frontend theme and secret keys.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# PostgreSQL for keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secured-identity-db&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;secured-identity-db &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres:16-alpine &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;6063&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;expose&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;6063&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./data/secured-identity-db:/var/lib/postgresql/data &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_PASSWORD=myStrongPassword123 &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_USER=keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_DB=keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;secured-network &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secured-identity&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;secured-identity &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;./Keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;start-dev&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;expose&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KEYCLOAK_ADMIN=admin &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KEYCLOAK_ADMIN_PASSWORD=admin &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_HOSTNAME_URL=http://localhost:5050/identity &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_DB=postgres &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_DB_USERNAME=keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_DB_PASSWORD=myStrongPassword123 &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_DB_URL=jdbc:postgresql://secured-identity-db:5432/keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;secured-identity-db &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;secured-network &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secured-network&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;driver&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;bridge &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secured-data&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;driver&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And we just need to run docker from this docker-compose.yml file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="put-keycloak-behind-ocelot-api-gateway-to-protect-resources"&gt;Put keycloak behind Ocelot API Gateway to protect resources&lt;/h2&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/Microservice-architecture-with-Ocelot-and-Keycloak.webp" alt="Microservice architecture with Ocelot and Keycloak" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Microservice architecture with Ocelot and Keycloak&lt;/p&gt;
&lt;p&gt;It is important to secure your identity service (keycloak) and reduce the attack vector by limiting the endpoints that can receive requests like other services. Keycloak admin panel should not be accessed from an external network, only people on this network can access this feature. For further readings consider visit Keycloak documentation about &lt;a href="https://www.keycloak.org/server/reverseproxy"&gt;Using a reverse proxy&lt;/a&gt;.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/Exposed-path-recommendations.webp" alt="Exposed path recommendations" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Exposed path recommendations&lt;/p&gt;
&lt;p&gt;First, let’s create Ocelot project. I use Dotnet 8&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln -n Secured
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new webapi -o Secured.ApiGateway
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd Secured.ApiGateway/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Ocelot --version 22.0.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After we successfully created the project, modify Program.cs as follows and create ocelot.json file in root directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Configuration.AddJsonFile(&lt;span style="color:#e6db74"&gt;&amp;#34;ocelot.json&amp;#34;&lt;/span&gt;, optional: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, reloadOnChange: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddOcelot(builder.Configuration);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.UseOcelot();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.RunAsync();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After that, we can use this ocelot.json file to limit access to our keycloak application running on localhost:5053.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/realms/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/identity/realms/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Post&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Put&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Delete&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/resources/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/identity/resources/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/js/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/identity/js/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;GlobalConfiguration&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;BaseUrl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://localhost:5050&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After this configurations you can access keycloak from localhost:5050 that is the API Gateway’s address. After these configurations, you can access keycloak from localhost:5050, which is the address of the API Gateway, but you will not be able to access the Keycloak admin panel through this address because we have limited access.&lt;/p&gt;
&lt;p&gt;For making our Keycloak configurations, we can log in to the admin panel at the keycloak address that is still accessible locally, that is, localhost:5053. Note that in the deployment scenario, we will ensure that only Ocelot has access to the external internet.&lt;/p&gt;
&lt;p&gt;In localhost:5053 we can enter &lt;strong&gt;admin&lt;/strong&gt; for username and &lt;strong&gt;admin&lt;/strong&gt; for password. We can change this credentials from docker-compose.yml file.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-1.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;After logged in we create new realm for managing users and client for our application from this dropdown menu. After clicking the Create realm button, simply enter the &lt;strong&gt;secured&lt;/strong&gt; as the realm name and leave everything as is.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-2.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;In clients tab click Create client. name Client ID as &lt;strong&gt;postman&lt;/strong&gt; and valid redirect uri as &lt;a href="https://oauth.pstmn.io/v1/callback"&gt;&lt;strong&gt;https://oauth.pstmn.io/v1/callback&lt;/strong&gt;&lt;/a&gt;. leave other things as is.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-3.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;In the users tab, add users to log in to Keycloak.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-4.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;After these steps we can login to our keycloak with newly created user to get access token. For accessing API gateway I use Postman. In postman you can logged in with Keycloak. In Authorization section select OAuth 2.0 and configure as follows and click Get New Access Token. Postman will open browser window and redirect you to Keycloak login page, enter credentials for your new user. Don’t enter admin credentials, this credentials only valid in master realm.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Grant type: Authorization code
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Auth URL: http://localhost:5050/identity/realms/secured/protocol/openid-connect/auth
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access Token URL: http://localhost:5050/identity/realms/secured/protocol/openid-connect/token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Client ID: postman
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Scope: openid profile roles
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you did everything right, you have successfully authenticated using Postaman with Keycloak behind the API Gateway 🎉&lt;/p&gt;
&lt;h2 id="authenticate-requests-to-other-services"&gt;Authenticate requests to other services&lt;/h2&gt;
&lt;p&gt;If you want to authenticate a request in Ocelot, make the following updates to Program.cs and ocelot.json. MetadataAddress gets the keys required for JWT validation. However, if you want to avoid extra network calls, you can place the keys in the appsettings.json file and use them.&lt;/p&gt;
&lt;p&gt;Authentication completed with AuthenticationOptions in ocelot.json file, here we say that we will send our JWT token along with the Bearer prefix and JWT token validation done with configuration from Program.cs&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.MetadataAddress = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050/identity/realms/secured/.well-known/openid-configuration&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.RequireHttpsMetadata = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.Authority = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050/realms/secured&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.Audience = &lt;span style="color:#e6db74"&gt;&amp;#34;account&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Configuration.AddJsonFile(&lt;span style="color:#e6db74"&gt;&amp;#34;ocelot.json&amp;#34;&lt;/span&gt;, optional: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, reloadOnChange: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddOcelot(builder.Configuration);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.UseOcelot();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.RunAsync();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/get&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;httpbin.org&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/test&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AuthenticationOptions&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AuthenticationProviderKey&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="authorize-requests-to-other-services"&gt;Authorize requests to other services&lt;/h2&gt;
&lt;p&gt;Authorization is slightly more difficult and requires additional work because Keycloak place user roles in nested form in JWT payload. It is something like following. As you can see roles field is placed under realm_access field. This creates confusion in Ocelot, because Ocelot reads claims as string or object values and does not evaluate nested fields. To avoid this problem we configure Keycloak token mapper and don’t use nested form.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;exp&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1706600524&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;realm_access&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;roles&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;offline_access&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;default-roles-microcommerce&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;uma_authorization&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;customer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;resource_access&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;account&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;roles&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;manage-account&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;manage-account-links&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;view-profile&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;openid email profile&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;preferred_username&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;berkslv&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For this custom mapping, click Client scopes from left menu and select roles.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-5.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;Under the roles, select Mappers section and then click realm roles.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-6.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;In the menu that opens, realm_access.roles is entered in the Token Claim Name option. We update this to &lt;strong&gt;realm_roles&lt;/strong&gt;.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-7.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;After making these configurations, we update our token by logging in again via Postman. We make the following update in the ocelot.json file so that the requests we make with the updated token are subject to claim control by Ocelot. If we don’t have &lt;strong&gt;customer&lt;/strong&gt; role in our user we get 403 response. You can create and assing your custom roles in Keycloak admin panel.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/get&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;httpbin.org&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/test&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AuthenticationOptions&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AuthenticationProviderKey&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;RouteClaimsRequirement&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;realm_roles&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;customer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want to access the source code, you can find the whole project on my GitHub account:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-ocelot-and-keycloak"&gt;GitHub - berkslv/lecture-ocelot-and-keycloak&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Achieving Zero Downtime: Azure App Service Deployment using Azure DevOps and Deployment Slots</title><link>https://berkselvi.dev/posts/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/</link><pubDate>Tue, 21 Nov 2023 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/</guid><description>&lt;p&gt;Hello! When deploying our application, which runs as a single instance in Azure App Service, using Azure DevOps pipelines, there will most likely be a few seconds of downtime. Because the application running as a single instance will need to be restarted to update it with a new version. In a worst case scenario, if an error occurs in the transition of our application versions, downtime will be extended due to rollback.&lt;/p&gt;</description><content>&lt;p&gt;Hello! When deploying our application, which runs as a single instance in Azure App Service, using Azure DevOps pipelines, there will most likely be a few seconds of downtime. Because the application running as a single instance will need to be restarted to update it with a new version. In a worst case scenario, if an error occurs in the transition of our application versions, downtime will be extended due to rollback.&lt;/p&gt;
&lt;p&gt;We can use the deployment slots feature to solve this problem specifically for the app service. With this feature, 2 different instances, usually staging and production applications, are running in a single app service plan as a separate instances, and when switching to production, the swap operation is managed by the app service and no downtime is experienced.&lt;/p&gt;
&lt;p&gt;In this post, we will create a very simple Dotnet Web API project, host it in the Azure DevOps repository, deploy it to Azure App Service by creating a CI/CD pipeline with the Azure DevOps pipeline, and perform zero downtime deployment with the deployment slots feature. We will complete the entire process in 5 steps as follows. Let’s begin.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating the application&lt;/li&gt;
&lt;li&gt;Azure DevOps Repo&lt;/li&gt;
&lt;li&gt;Azure DevOps build pipeline&lt;/li&gt;
&lt;li&gt;Azure App service&lt;/li&gt;
&lt;li&gt;Zero downtime testing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;## Creating the application&lt;/p&gt;
&lt;p&gt;We create a simple Dotnet Web API project to deploy. To do this, we create our project with the following commands.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir backend
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ./backend
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln -n Slot
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new webapi -n Slot.API
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln add ./Slot.API/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We update the &lt;em&gt;profiles.http&lt;/em&gt; property in the &lt;em&gt;properties/launchSetting.json&lt;/em&gt; file as follows. Here we only updated the &lt;em&gt;applicationUrl&lt;/em&gt; and &lt;em&gt;launchBrowser&lt;/em&gt; properties.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;profiles&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;commandName&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Project&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;dotnetRunMessages&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;launchBrowser&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;launchUrl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;swagger&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;applicationUrl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;environmentVariables&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;ASPNETCORE_ENVIRONMENT&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Development&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By making the following update on Program.cs, we will test that the application is accessible and can meet the requests made to the /health endpoint. If you are using a database in your application, you can also test whether there is a problem accessing the database with &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.entityframeworkcorehealthchecksbuilderextensions.adddbcontextcheck?view=dotnet-plat-ext-8.0"&gt;AddDbContextCheck&lt;/a&gt; method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHealthChecks();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// app.UseHttpsRedirection(); &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseHealthChecks(&lt;span style="color:#e6db74"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s it for our application! Now we can access the localhost:5050/health endpoint by running&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ./Slot.API
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet run
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -4 http://localhost:5050/health
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Healthy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Later, we will use Docker to deploy our application, so we put our Dockerfile file in the same directory as our sln file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Build Stage &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;mcr.microsoft.com/dotnet/aspnet:7.0-alpine&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;base&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXPOSE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8080&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Publish Stage &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;mcr.microsoft.com/dotnet/sdk:7.0-alpine&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;build&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API/Slot.API.csproj&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API/&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; dotnet restore &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API/Slot.API.csproj&amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; . . &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/Slot.API&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; dotnet build &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API.csproj&amp;#34;&lt;/span&gt; -c Release -o /app/build &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;build&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;publish&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; dotnet publish &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API.csproj&amp;#34;&lt;/span&gt; -c Release -o /app/publish /p:UseAppHost&lt;span style="color:#f92672"&gt;=&lt;/span&gt;false &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;base&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;final&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;publish /app/publish . &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; ASPNETCORE_URLS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;http://*:8080 &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; ASPNETCORE_ENVIRONMENT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Production
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENTRYPOINT&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;dotnet&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API.dll&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can use the following two commands to ensure that we can successfully build the image and run the container.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker build -t deployment-slots-demo .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -it -p 80:8080 deployment-slots-demo -n deployment-slots-demo-container
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="verify-that-the-application-is-up"&gt;Verify that the application is up&lt;/h2&gt;
&lt;p&gt;After creating our application with the health check feature in the previous step, we can verify that the application is accessible by making a request to this endpoint every second. This way, we will verify that it is accessible when deployed to Azure.&lt;/p&gt;
&lt;p&gt;In this section, we will write a simple Node.js script to verify that the application can be accessed by making requests every second. We use the following commands for this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir health-check
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm init -y
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;touch index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install node-fetch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We add the following script to the package.json file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;scripts&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;start&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;node index.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#960050;background-color:#1e0010"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;module&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can create our index.js file as follows. With this code, we will make a request to the given url every 50 milisecond and write the response to the console.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;node-fetch&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Healthy&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; result is not OK`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; error is &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you do not turn off the UseHttpsRedirection middleware in our API project, you may receive an invalid SSL certificate error. You can fix this as follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;node-fetch&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpsAgent&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Agent&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;rejectUnauthorized&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;agent&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpsAgent&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Healthy&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; result is not OK`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; error is &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;https://localhost:5051/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;https://localhost:5051/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="azure-devops-repo"&gt;Azure DevOps Repo&lt;/h2&gt;
&lt;p&gt;We log into our Azure DevOps account and create a new repo as follows.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-1.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-2.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;We will clone this repo to our computer, move the code that we write into this repo folder and push the codes to origin.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-3.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git commit -m &lt;span style="color:#e6db74"&gt;&amp;#34;inital commit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git push origin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-4.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;h2 id="azure-devops-build-pipeline"&gt;Azure DevOps build pipeline&lt;/h2&gt;
&lt;p&gt;On the Pipeline screen, we click on the New pipeline button from the top right. Then, we build the docker file and push it to Azure Container Registry by following the steps below.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-5.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-6.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-7.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-8.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-9.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-10.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-11.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-12.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-13.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-14.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-15.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-16.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-17.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-18.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-19.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-20.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;Now we check that our image deployment in Azure Container Registry is successful.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-21.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-22.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-23.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;As we can see, when the pipeline is run, the docker image has been successfully created in the Azure Container Registry and is waiting to be used. After these settings, a new docker image will be created by triggering every change made in the /backend directory on the main branch.&lt;/p&gt;
&lt;h2 id="azure-app-service"&gt;Azure App service&lt;/h2&gt;
&lt;p&gt;Azure App service allows us to deploy web applications with security, load balancing, autoscaling managed by Azure itself. We can create a new app service via the Azure app services screen as follows.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-24.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-25.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-26.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-27.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-28.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;After creating the app service, you may need to update the Container Registry password from the configuration tab.&lt;/p&gt;
&lt;p&gt;## Azure DevOps release pipeline&lt;/p&gt;
&lt;p&gt;With the release pipeline, we deploy to the App service using the Docker image produced by the build pipeline and publish our application.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-29.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-30.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-31.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-32.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-33.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-34.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-35.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;By going into staging deployment, we get deployment for our staging version and restart our app service to ensure that the docker image change we made goes live.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-36.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-37.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-38.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-39.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-40.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-41.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;In our production step, we do not take a deployment to the app service, instead we perform the swap operation with staging and in this way, we start running our application running in staging in production without experiencing any downtime.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-42.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-43.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-44.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-45.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;From now on, any changes made to the main branch will first run the build pipeline (CI), then the deployment pipeline (CD), and deployment will be made to the staging environment. If you want to switch to Production, Production deployment must be triggered manually from the release screen.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-46.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;h2 id="zero-downtime-testing"&gt;Zero downtime testing&lt;/h2&gt;
&lt;p&gt;We have successfully created a pipeline for our application, from now on we will need to test whether there is any downtime during deployment. For this, I update our &lt;em&gt;health-check/index.js&lt;/em&gt; file as follows and run the application with the npm run start command and trigger the pipeline. And then I complete the deployment process without receiving any error messages on the console, that is, without any downtime!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;node-fetch&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Healthy&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; result is not OK`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; error is &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;https://deployment-slot-demo.azurewebsites.net/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;https://deployment-slot-demo-staging.azurewebsites.net/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we try a similar process in an app service deployment that does not implement deployment slots, we will receive an error as follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2023-11-14T12:28:39.506Z, &lt;span style="color:#f92672"&gt;[&lt;/span&gt;some-url&lt;span style="color:#f92672"&gt;]&lt;/span&gt;/health error is request to &lt;span style="color:#f92672"&gt;[&lt;/span&gt;some-url&lt;span style="color:#f92672"&gt;]&lt;/span&gt;/health failed, reason: connect ETIMEDOUT 100.100.100.100:443
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>Background jobs and Hangfire in .NET</title><link>https://berkselvi.dev/posts/background-jobs-and-hangife-in-net/</link><pubDate>Sun, 17 Sep 2023 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/background-jobs-and-hangife-in-net/</guid><description>&lt;p&gt;When developing an application in the .Net ecosystem, when things get complicated, we may need some of our methods to go to multiple services, evaluate their responses, and report these results to different services and this takes long time! We do not want to waste resources by placing such long-running methods behind an endpoint and keeping our TCP connection open for the response from that HTTP request. We can call this usage on demand job because it will run when a request is made. We may also want it to run automatically at certain times of the day or week, without putting it behind an endpoint; in this case, we can call it a recurring job. In such cases, we use “background job” by starting our work on a different thread other than the main thread where the application runs.&lt;/p&gt;</description><content>&lt;p&gt;When developing an application in the .Net ecosystem, when things get complicated, we may need some of our methods to go to multiple services, evaluate their responses, and report these results to different services and this takes long time! We do not want to waste resources by placing such long-running methods behind an endpoint and keeping our TCP connection open for the response from that HTTP request. We can call this usage on demand job because it will run when a request is made. We may also want it to run automatically at certain times of the day or week, without putting it behind an endpoint; in this case, we can call it a recurring job. In such cases, we use “background job” by starting our work on a different thread other than the main thread where the application runs.&lt;/p&gt;
&lt;p&gt;There are many different methods of managing background jobs in .Net. This will be a blog post where I will talk about them step by step, how they solve problems and what problems they cause us, and give in-depth usage examples in Hangfire, which is the package I like the most when dealing with background jobs. You can also access all the codes from the repo:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-dotnet-background-jobs"&gt;GitHub - berkslv/lecture-dotnet-background-jobs&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="taskrun"&gt;Task.Run()&lt;/h2&gt;
&lt;p&gt;We can create a new thread and run a method on it with the Task.Run() method, which is the first solution that comes to my mind when I need a background job. I have a method that has dependencies on many external services that can take up to 5 minutes to complete, and I was calling this method with an HTTP request through a controller that I created, but answering this request was problematic because it is required a TCP connection to remain open for 5 minutes, so instead when I called the method, If started successfully, I had to notify the client that the process was started successfully with a 200 status code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// TestService.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TestService&lt;/span&gt; : ITestService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;TestService&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; TestService(ILogger&amp;lt;TestService&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; RunTests()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} RunTests is started&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(&lt;span style="color:#ae81ff"&gt;5000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} RunTests is finished&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;ITestService, TestService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If I do not make the execution by calling with the Task.Run(), the request to my /job endpoint will be loading for 3 seconds in browser for this example.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// JobController.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JobController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ITestService _testService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; JobController(ITestService testService)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _testService = testService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Get()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _testService.RunTests();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Ok&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If I use Task.Run instead as below, the controller will respond ok and my RunTests method will continue to run in the background.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Get()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task.Run(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _testService.RunTests();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Ok&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="pros"&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;We can run a process in the background on demand.&lt;/li&gt;
&lt;li&gt;Does not require an additional package.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cons"&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;It does not support recurring in its current state, a special system must be developed.&lt;/li&gt;
&lt;li&gt;When Dependency Injection is used, since the injected interfaces will remain on the main thread, we may need to re-generate the required interface through the Service scope.&lt;/li&gt;
&lt;li&gt;What happens if an error is throwed while running the Method?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hosted-service"&gt;Hosted Service&lt;/h2&gt;
&lt;p&gt;We had to develop our own system to manage recurring jobs, which we could not implement in our previous example, but with the Hosted service, we do not need to develop this management ourselves, instead we call our &lt;code&gt;AddHostedService&lt;/code&gt; method in Program.cs as follows and inherit our TestService class from the BackgroundService class. In this example, our RunTests method will run every 10 seconds. This time interval is set from the ExecuteAsync method inherited from BackgroundService abstract class.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHostedService&amp;lt;TestService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// TestService.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TestService&lt;/span&gt; : BackgroundService, ITestService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;TestService&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; TestService(ILogger&amp;lt;TestService&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; RunTests(TestType testType)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; type = Enum.GetName(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(TestType), testType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is started&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(&lt;span style="color:#ae81ff"&gt;5000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is finished&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ExecuteAsync(CancellationToken stoppingToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; timer = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; PeriodicTimer(TimeSpan.FromSeconds(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; timer.WaitForNextTickAsync(stoppingToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RunTests(TestType.Recurring);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, if we want to run our method on demand, we work on a new thread with the help of the Task.Run() method, so we do not need to make any changes to JobController.cs.&lt;/p&gt;
&lt;h3 id="pros-1"&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Provides recurring job management&lt;/li&gt;
&lt;li&gt;No need to install additional packages&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cons-1"&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;It does not have a system for on demand operation.&lt;/li&gt;
&lt;li&gt;What happens if an error is received while running the Method?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hangfire"&gt;Hangfire&lt;/h2&gt;
&lt;p&gt;Hangfire makes our job much easier to manage on demand and recurring jobs through a single system. With the Job Storage system, which is not available in the other two methods, if the application is not running at that moment but the cron job has expired, it automatically runs the relevant job. We can run specific jobs and delete that job with the ID information provided during job creation. Additionally, we can monitor currently running jobs via a dashboard at /hangfire.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/background-jobs-and-hangife-in-net/hangfire-dashboard.webp" alt="Hangfire dashboard" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Hangfire dashboard&lt;/p&gt;
&lt;p&gt;Hangfire officially supports the Sql Server database, but with an open source extension, frequently preferred databases such as Sqlite and Postgresql can also be used. In addition, Hangfire, with its paid version, also meets enterprise needs such as Redis database support and running batch jobs.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hangfire.io/extensions.html"&gt;Hangfire – Background Jobs for .NET and .NET Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After this brief introduction to Hangfire, let’s briefly talk about how we can use Hangfire in our application and its capabilities. In our example, we will use Postgresql as Job storage, but you can choose any database you want from the link above. After installing the following packages in our application and setting up a Postgre SQL database with docker, let’s move on to our code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Hangfire
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Hangfire.Core
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Hangfire.PostgreSql
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package TimeZoneConverter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --name postgres_db -e POSTGRES_USER&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;root&amp;#34;&lt;/span&gt; -e POSTGRES_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;1234&amp;#34;&lt;/span&gt; -e POSTGRES_DB&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt; -v postgres_data:/var/lib/postgresql/data -p 5432:5432 postgres
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, we provide the connection string information for Postgresql in appsettings.json as follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;ConnectionStrings&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;HangfireConnection&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Host=localhost;Port=5432;Password=1234;Username=root;Database=postgres;Pooling=true;Integrated Security=true;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We make the relevant configurations in Program.cs as follows.&lt;/p&gt;
&lt;p&gt;With TZConvert.GetTimeZoneInfo method you get the neccessary time zone information from OS. This line of code is neccessary because your local machine, frontend application and Cloud machine may have different time zones.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHangfire(config =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UseSimpleAssemblyNameTypeSerializer()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UseRecommendedSerializerSettings()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UsePostgreSqlStorage(builder.Configuration.GetConnectionString(&lt;span style="color:#e6db74"&gt;&amp;#34;HangfireConnection&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; cronEveryMinute = &lt;span style="color:#e6db74"&gt;&amp;#34;*/1 * * * *&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; recurringJobOptions = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; RecurringJobOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TimeZone = TZConvert.GetTimeZoneInfo(&lt;span style="color:#e6db74"&gt;&amp;#34;Etc/GMT+3&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecurringJob.AddOrUpdate&amp;lt;ITestService&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;id-run-and-wait&amp;#34;&lt;/span&gt;, x =&amp;gt; x.RunTests(Guid.NewGuid(), TestType.Recurring, CancellationToken.None), cronEveryMinute, recurringJobOptions);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHangfireServer();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseHangfireDashboard();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapHangfireDashboard();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To showcase Hangfire’s capabilities, we add a few more endpoints to our controller class.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/run,&lt;/strong&gt; we can start a job and that method returns us a job id&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/stop,&lt;/strong&gt; we can stop the job related to the job id given to us by Hangfire.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/continue,&lt;/strong&gt; if many different jobs are to be run but they are dependent on each other, another job can be run after the parent job is finished with the given job id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/reschedule,&lt;/strong&gt; the job’s working intervals can be dynamically adjusted by cron or TimeSpan.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/deschedule,&lt;/strong&gt; recurring jobs can be deleted by their unique id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/trigger,&lt;/strong&gt; we can manually trigger a recurring job.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// JobController.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JobController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/run&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Run()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; jobId = BackgroundJob.Enqueue&amp;lt;ITestService&amp;gt;(x =&amp;gt; x.RunTests(Guid.NewGuid(), TestType.OnDemand, CancellationToken.None));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(jobId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/stop&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Stop(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; jobId)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BackgroundJob.Delete(jobId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Stopped&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/continue&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Continue(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; jobId)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BackgroundJob.ContinueJobWith&amp;lt;ITestService&amp;gt;(jobId, x =&amp;gt; x.RunTests(Guid.NewGuid(), TestType.OnDemand, CancellationToken.None));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Continued&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/reschedule&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Reschedule(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; cron)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; recurringJobOptions = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; RecurringJobOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TimeZone = TZConvert.GetTimeZoneInfo(&lt;span style="color:#e6db74"&gt;&amp;#34;Etc/GMT+3&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecurringJob.AddOrUpdate&amp;lt;ITestService&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;id-run-and-wait&amp;#34;&lt;/span&gt;, x =&amp;gt; x.RunTests(Guid.NewGuid(), TestType.Recurring, CancellationToken.None), cron, recurringJobOptions);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Rescheduled&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/deschedule&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Deschedule(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (String.IsNullOrEmpty(id))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id = &lt;span style="color:#e6db74"&gt;&amp;#34;id-run-and-wait&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecurringJob.RemoveIfExists(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Descheduled&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/trigger&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Trigger(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (String.IsNullOrEmpty(id))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id = &lt;span style="color:#e6db74"&gt;&amp;#34;id-run-and-wait&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecurringJob.TriggerJob(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Triggered&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, error management, which is not in our toolkit previously with Task.Run() and Hosted service but with Hangfire if an error occurs while running a method, Hangfire runs that method 10 more times with the same parameters at certain time intervals. As an example, we add a method called ThrowRandomly to our TestService class. With this method, we simply add a system that will throw an exception from the method that works with probability 1/2, but Hangfire will try to get successful results by re-running the methods that get errors for us. But errors that catches successfully cannot trigger the retry system. Therefore in the end of catch block we throw again.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// TestService.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TestService&lt;/span&gt; : ITestService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;TestService&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; TestService(ILogger&amp;lt;TestService&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; RunTests(Guid id, TestType testType, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; type = Enum.GetName(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(TestType), testType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is started. Id: {id}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cancellationToken.ThrowIfCancellationRequested();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(&lt;span style="color:#ae81ff"&gt;5000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ThrowRandomly();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is finished. Id: {id}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (OperationCanceledException exception)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogError(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is failed. Exception: {exception.Message} Id: {id}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;(Exception exception)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogError(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is failed. Exception: {exception.Message} Id: {id}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ThrowRandomly()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; random = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Random();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; number = random.Next(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (number == &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Exception(&lt;span style="color:#e6db74"&gt;&amp;#34;Error is throwed!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also Hangfire runs on a seperate service, this can be good or bad depending on your needs. When you need scaling you may seperate the application and hangfire servers and place them to different machines.&lt;/p&gt;
&lt;h3 id="pros-2"&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Can manage on demand and recurring jobs together with a powerful abstraction&lt;/li&gt;
&lt;li&gt;We can adjust cron job timing dynamically and its timing is very precise.&lt;/li&gt;
&lt;li&gt;We can monitor our employee and cron jobs with Dashborad.&lt;/li&gt;
&lt;li&gt;There is no imposed interface implementation or any other special implementation, we can only manage our jobs by using the methods provided by Hangfire.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cons-2"&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;External storage is required, works with SQL Server by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>How to Secure Dotnet &amp; Vue.js Application with Keycloak</title><link>https://berkselvi.dev/posts/how-to-secure-dotnet-vue-application-with-keycloak/</link><pubDate>Mon, 21 Aug 2023 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/how-to-secure-dotnet-vue-application-with-keycloak/</guid><description>&lt;p&gt;In this post, I will talk about how we can secure our dotnet and vue applications using keycloak.&lt;/p&gt;
&lt;p&gt;First, we run our keycloak application using docker, if you publish this application using Azure or a similar cloud provider, you can easily use it in your production applications.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 8080:8080 -e KEYCLOAK_ADMIN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;admin -e KEYCLOAK_ADMIN_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;admin quay.io/keycloak/keycloak:22.0.1 start-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After Keycloak is running, we create realm for our application from the left side after logging in from localhost:8080/admin with username: admin and password: admin. Since master realm is assigned to keycloak’s own use, we need to work on another realm.&lt;/p&gt;</description><content>&lt;p&gt;In this post, I will talk about how we can secure our dotnet and vue applications using keycloak.&lt;/p&gt;
&lt;p&gt;First, we run our keycloak application using docker, if you publish this application using Azure or a similar cloud provider, you can easily use it in your production applications.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 8080:8080 -e KEYCLOAK_ADMIN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;admin -e KEYCLOAK_ADMIN_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;admin quay.io/keycloak/keycloak:22.0.1 start-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After Keycloak is running, we create realm for our application from the left side after logging in from localhost:8080/admin with username: admin and password: admin. Since master realm is assigned to keycloak’s own use, we need to work on another realm.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-secure-dotnet-vue-application-with-keycloak/create-realm.webp" alt="Create realm" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Create realm&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-secure-dotnet-vue-application-with-keycloak/create-realm-confirmation.webp" alt="Create realm confirmation" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Create realm confirmation&lt;/p&gt;
&lt;p&gt;After creating the realm, we create a client called vue from the client section. Our redirect urls are the url of our Vue app. You may need to make additional updates for production.&lt;/p&gt;
&lt;p&gt;We can create users under dotnet-vue realm from the Users menu to be used in our vue application.&lt;/p&gt;
&lt;p&gt;If we want to create a user for our application, we can enable our users to register with keycloak by turning on this option in realm settings.&lt;/p&gt;
&lt;h2 id="vue"&gt;Vue&lt;/h2&gt;
&lt;p&gt;After completing these configurations, we are done with the keycloak admin panel, when an unauthorized request comes to our vue application, we have to redirect it to our keycloak login page. For this, we use the medium article &lt;a href="https://medium.com/keycloak/secure-vue-js-app-with-keycloak-94814181e344"&gt;Secure Vue.js app with Keycloak&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We can create our Vue application using vite as follows, you can choose Vue and Javascript from the questions asked and continue.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm create vite@latest vue
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd vue
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm run dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then we update the main.js content as follows. This script simply redirects the unauthorized requests to login and updates the token with the refresh token if the token expires with onTokenExpired. After this process, if we run the application with npm run dev and go to http://localhost:5173, the keycloak login url will be redirected. If we log in with the user information we have created, we will be logged into our system.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;createApp&lt;/span&gt; } &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;./style.css&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;App&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;./App.vue&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Keycloak&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;keycloak-js&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;initOptions&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8080&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;realm&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;dotnet-vue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientId&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;onLoad&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;login-required&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Keycloak&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;initOptions&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;init&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;onLoad&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;initOptions&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;onLoad&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;auth&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;auth&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; window.&lt;span style="color:#a6e22e"&gt;location&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;reload&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Authenticated&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;createApp&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;App&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;mount&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;#app&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Authenticated Failed&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;onTokenExpired&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Token expired&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;updateToken&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Token renewed&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;login&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="dotnet"&gt;Dotnet&lt;/h2&gt;
&lt;p&gt;We can create a webapi project using Dotnet 6 as follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd dotnet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln -n Secured
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new webapi -o Secured.API
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd Secured.API
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet watch run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks to the standards supported by Keycloak and Dotnet, we can only authorize with the configurations we will make in Program.cs without writing a special code. In this code, we verify where the token came from with the issuer, and thanks to IssuerSigningKey, we can verify the generated token without going to the keycloak.&lt;/p&gt;
&lt;p&gt;RequireHttpsMetadata should only be used for development enviroment, if you have an application running in production enviroment you can delete this line and its value will evaluate to true.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSwaggerGen();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; issuer = builder.Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; key = builder.Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Key&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJwtBearer(o =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.Authority = issuer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.RequireHttpsMetadata = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.TokenValidationParameters = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Microsoft.IdentityModel.Tokens.TokenValidationParameters
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuer = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateAudience = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateLifetime = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuerSigningKey = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidIssuer = issuer,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IssuerSigningKey = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can access it at builder.Configuration by keeping the data as below in the appsettings.json file. You can access the RSA256 key from the RSA256 Kid value in the Keys tab in the Realm settings menu under the relevant realm in the keycloak admin panel.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Issuer&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://localhost:8080/realms/dotnet-vue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Key&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;secret-rsa-key&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#960050;background-color:#1e0010"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="consuming-dotnet-api-from-vue-app"&gt;Consuming Dotnet API from Vue app&lt;/h2&gt;
&lt;p&gt;First we add CORS settings and Authorize attributes in our dotnet project. For this, we edit &lt;code&gt;Program.cs&lt;/code&gt; as follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddCors(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.AddDefaultPolicy(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; policy =&amp;gt; policy.WithOrigins(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5137&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AllowAnyHeader()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AllowAnyMethod()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AllowAnyOrigin()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// app.UseHttpsRedirection(); &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseCors();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The WeatherForecastController.cs file provides security with the following attribute.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Authorize]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;WeatherForecastController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, we install the axios library in our vue application with the following command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install axios
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After installation, we create a folder named services and make axios configurations in our file named &lt;code&gt;base.api.js&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;axios&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;axios&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;../main&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;axios&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;baseURL&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;interceptors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;token&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;localStorage&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getItem&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;vue-token&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Authorization&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`Bearer &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;token&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Accept&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Promise.&lt;span style="color:#a6e22e"&gt;reject&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To use the WeatherForecast endpoint, which is an example endpoint, we create the &lt;code&gt;weather.api.js&lt;/code&gt; file in the same directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;axios&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;./base.api&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getWeather&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;axios&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`/WeatherForecast`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We display the data by making our API request in &lt;code&gt;App.vue&lt;/code&gt;, here we can log out with the logout method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-vue" data-lang="vue"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Keycloak&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;./main&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;getWeather&lt;/span&gt; } &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;./service/weather.api&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;App&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;weathers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;async&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mounted&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;weathers&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getWeather&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;methods&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;logout&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Keycloak&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;logout&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;button&lt;/span&gt; &lt;span style="color:#f92672"&gt;@click&lt;/span&gt;&lt;span style="color:#e6db74"&gt;=&amp;#34;logout&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;style&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;margin-bottom: 30px;&amp;#34;&lt;/span&gt;&amp;gt;&lt;span style="color:#a6e22e"&gt;Logout&lt;/span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;table&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;th&lt;/span&gt;&amp;gt;Date&amp;lt;/&lt;span style="color:#f92672"&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;th&lt;/span&gt;&amp;gt;&lt;span style="color:#a6e22e"&gt;Summary&lt;/span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;tr&lt;/span&gt; &lt;span style="color:#f92672"&gt;v-for&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;weather in weathers&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;td&lt;/span&gt;&amp;gt;{{ &lt;span style="color:#a6e22e"&gt;weather&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;date&lt;/span&gt; }}&amp;lt;/&lt;span style="color:#f92672"&gt;td&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;td&lt;/span&gt;&amp;gt;{{ &lt;span style="color:#a6e22e"&gt;weather&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;summary&lt;/span&gt; }}&amp;lt;/&lt;span style="color:#f92672"&gt;td&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;table&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want to access the source code, you can find the whole project on my GitHub account:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-dotnet-vue-keycloak"&gt;GitHub - berkslv/lecture-dotnet-vue-keycloak&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>How to Deploy a React App with Nginx using Docker with react-router-dom</title><link>https://berkselvi.dev/posts/how-to-deploy-a-react-app-with-nginx-using-docker-with-react-router-dom/</link><pubDate>Sat, 21 Jan 2023 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/how-to-deploy-a-react-app-with-nginx-using-docker-with-react-router-dom/</guid><description>&lt;p&gt;If you&amp;rsquo;re looking to deploy a React app, there are several options available, including deploying it to a web server or using a containerization tool like Docker. In this tutorial, we&amp;rsquo;ll show you how to deploy a React app with Nginx using Docker. Nginx is a popular open-source web server that is known for its high performance and low resource usage. We&amp;rsquo;ll create a Dockerfile for our React app that uses Nginx to serve the app, and we&amp;rsquo;ll also create an Nginx configuration file to tell Nginx how to serve the app. Finally, we&amp;rsquo;ll build a Docker image of our app and run a Docker container to deploy the app.&lt;/p&gt;</description><content>&lt;p&gt;If you&amp;rsquo;re looking to deploy a React app, there are several options available, including deploying it to a web server or using a containerization tool like Docker. In this tutorial, we&amp;rsquo;ll show you how to deploy a React app with Nginx using Docker. Nginx is a popular open-source web server that is known for its high performance and low resource usage. We&amp;rsquo;ll create a Dockerfile for our React app that uses Nginx to serve the app, and we&amp;rsquo;ll also create an Nginx configuration file to tell Nginx how to serve the app. Finally, we&amp;rsquo;ll build a Docker image of our app and run a Docker container to deploy the app.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A basic understanding of Docker&lt;/li&gt;
&lt;li&gt;A React app to deploy&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="step-1-create-a-dockerfile"&gt;Step 1: Create a Dockerfile&lt;/h2&gt;
&lt;p&gt;The first step is to create a Dockerfile for our React app. This file will contain instructions on how to build a Docker image of our app. Here is an example Dockerfile for a React app with Nginx:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Use an official Node runtime as a parent image&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;node:19-alpine&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#e6db74"&gt;build&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Set the working directory to /app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the package.json and package-lock.json to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; package*.json ./&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Install dependencies&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; npm install&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the rest of the application code to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; . .&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Build the React app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; npm run build&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Use an official Nginx runtime as a parent image&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;nginx:1.21.0-alpine&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the ngnix.conf to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; ngnix.conf /etc/nginx/conf.d/default.conf&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the React app build files to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;build /app/build /usr/share/nginx/html&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Expose port 80 for Nginx&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXPOSE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;80&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Start Nginx when the container starts&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-g&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;daemon off;&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This Dockerfile uses the official Node runtime as the parent image to install and build the React app. It then uses the official Nginx runtime as the parent image to serve the React app.&lt;/p&gt;
&lt;h2 id="step-2-create-an-nginx-configuration-file"&gt;Step 2: Create an Nginx configuration file&lt;/h2&gt;
&lt;p&gt;Next, we need to create an Nginx configuration file to tell Nginx how to serve our React app. Here is an example configuration file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-conf" data-lang="conf"&gt;server {
listen 80;
server_name example.com;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;You must create this &lt;code&gt;nginx.conf&lt;/code&gt; file in the same directory with Dockerfile.&lt;/strong&gt; This configuration file sets up Nginx to listen on port 80 and serve the React app. The try_files directive tells Nginx to try serving the requested file, then the directory containing the file, and finally fall back to serving the index.html file if the requested file or directory doesn&amp;rsquo;t exist.&lt;/p&gt;
&lt;h2 id="step-3-build-the-docker-image"&gt;Step 3: Build the Docker image&lt;/h2&gt;
&lt;p&gt;With the Dockerfile and Nginx configuration file created, we can now build the Docker image. Navigate to the directory containing the Dockerfile and run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker build -t my-react-app .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command tells Docker to build an image using the Dockerfile in the current directory and tag it with the name my-react-app. The . at the end of the command tells Docker to use the current directory as the build context.&lt;/p&gt;
&lt;h2 id="step-4-run-the-docker-container"&gt;Step 4: Run the Docker container&lt;/h2&gt;
&lt;p&gt;With the Docker image built, we can now run a container using the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 80:80 my-react-app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command creates a container from the my-react-app Docker image and maps port 80 in the container to port 80 on the host machine. You can then access your React app by navigating to http://localhost in your web browser.&lt;/p&gt;
&lt;p&gt;Congratulations, you have successfully deployed a React app with Nginx using Docker! You can now use this method to easily deploy your React apps to any server or hosting platform that supports Docker.&lt;/p&gt;
&lt;h2 id="step-5-use-docker-compose-to-deploy-the-app"&gt;Step 5: Use Docker Compose to deploy the app&lt;/h2&gt;
&lt;p&gt;If you prefer a more streamlined approach to deploying your app, you can use Docker Compose to manage your containers. Docker Compose is a tool that allows you to define and run multi-container Docker applications.&lt;/p&gt;
&lt;p&gt;To use Docker Compose, you&amp;rsquo;ll need to create a docker-compose.yml file in the root directory of your project. Here&amp;rsquo;s an example docker-compose.yml file that you can use with the Dockerfile provided earlier:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;my-react-app&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;context&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dockerfile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Dockerfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;80:80&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./ngnix.conf:/etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this docker-compose.yml file, we define a single service named my-react-app. We specify the build context as the current directory (.), and the name of the Dockerfile as Dockerfile. We also map port 80 in the container to port 80 on the host machine using the ports configuration.&lt;/p&gt;
&lt;p&gt;Finally, we mount a volume that maps the ngnix.conf file on the host machine to the default.conf file in the container&amp;rsquo;s Nginx configuration directory. This allows us to customize the Nginx configuration without having to rebuild the Docker image.&lt;/p&gt;
&lt;p&gt;To use this docker-compose.yml file, simply navigate to the directory containing the file and run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command tells Docker Compose to build the my-react-app service using the Dockerfile and run a container. The -d flag runs the container in detached mode, which means that it runs in the background. You can then access your React app by navigating to http://localhost in your web browser.&lt;/p&gt;
&lt;p&gt;Congratulations! You have successfully deployed a React app with Nginx using Docker and Docker Compose. You can now easily deploy your React apps to any server or hosting platform that supports Docker.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>How to learn something very well</title><link>https://berkselvi.dev/posts/how-to-learn-something-very-well/</link><pubDate>Wed, 09 Mar 2022 10:01:10 +0300</pubDate><guid>https://berkselvi.dev/posts/how-to-learn-something-very-well/</guid><description>&lt;p&gt;When you learn how to learn, you unlock almost all the possibilities in the world with knowledge. Some of you can think practice more important than knowledge, but trust me, if you don’t know something about your working area, you won’t get practice, and if you can, it won’t work, because you don’t know the underlying processes.&lt;/p&gt;
&lt;p&gt;This guide is simple and results oriented, you can learn more about it with &lt;a href="https://www.goodreads.com/book/show/18770267-make-it-stick"&gt;this great book&lt;/a&gt;.&lt;/p&gt;</description><content>&lt;p&gt;When you learn how to learn, you unlock almost all the possibilities in the world with knowledge. Some of you can think practice more important than knowledge, but trust me, if you don’t know something about your working area, you won’t get practice, and if you can, it won’t work, because you don’t know the underlying processes.&lt;/p&gt;
&lt;p&gt;This guide is simple and results oriented, you can learn more about it with &lt;a href="https://www.goodreads.com/book/show/18770267-make-it-stick"&gt;this great book&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="before-the-learning"&gt;Before the learning&lt;/h2&gt;
&lt;p&gt;Mind is not fixed and can be developed, not only mind and IQ, intellectual skills also be developed with working. First of all you must have iron-strong self discipline, this can be earned in different ways, but you must find and follow your own path. If you wish, you can follow the military discipline methods, or you can stay loyal to the Far East philosophies. It’s entirely up to you.&lt;/p&gt;
&lt;p&gt;And remember, valuable things like money accumulate. This includes knowledge, and the more you learn new things, the easier it is to learn and make sense of new things.&lt;/p&gt;
&lt;h2 id="not-the-learning"&gt;Not the learning&lt;/h2&gt;
&lt;p&gt;Studying the same subject over and over, reading the same things over and over can create a sense of learning, but this is most likely a wrong feeling. sense of learning and fluency can easily be confused with each other.&lt;/p&gt;
&lt;p&gt;Don’t go ahead without testing yourself, you might think you learned that way, but most likely it also will be a false feeling.&lt;/p&gt;
&lt;h2 id="things-to-do"&gt;Things to do&lt;/h2&gt;
&lt;p&gt;Recalling from memory is a much more wonderful method compared to methods such as repeated reading. When reading articles, pause at the beginning of sentences or paragraphs and ask, “What is the main idea? What is the message you want to give?” asking questions like this is a great practice for our callback method. In this way, mental connections related to the subjects learned will be strengthened.&lt;/p&gt;
&lt;p&gt;End-of-chapter questions and self-created questions are great learning methods. It can be used here in quizzes and exams, Using Quizzes as a learning tool may be unorthodox in terms of danger, but works very well for recall and your answers to the questions are important. You can reveal your shortcomings in this way and completing these areas will move you forward.&lt;/p&gt;
&lt;p&gt;These two methods are all about callback. The final word on the recall is that the more challenging, the more permanent its effects will be.&lt;/p&gt;
&lt;p&gt;Instead of using the short-term benefits of block work, approaching the subject with intermittent work will make the knowledge more permanent. The required range depends on the subject and person being studied. Like 1 day, 1 week, 1 month. Trying to remember a subject periodically makes it permanent in the memory and now it becomes like a reflex.&lt;/p&gt;
&lt;p&gt;Solving similar questions on a math topic one after another creates a feeling of familiarity, but it does not help you see the differences and similarities with other questions on the same topic at the time of practice. If you notice that you are repeating questions while solving questions, immediately switch to different types of questions.&lt;/p&gt;
&lt;p&gt;Adding new layers of meaning to the subject you are learning is elaboration. An example of this is expressing a topic in your own words while taking notes and examining its relationship with real life. Examining the relationship of the newly learned subject with what you have learned before increases learning. Relate the topic to yourself and your life.&lt;/p&gt;
&lt;p&gt;Produce before learning. Trying to solve a math question with what you know before the answer is given makes it easier for you to grasp and remember the solution after the solution is explained.&lt;/p&gt;
&lt;p&gt;Blend recall and detailing. After a topic or class, ask yourself, “How did it go? Could it be better? Does this remind you of something from the past?” Asking questions like these helps learning, and this is called &lt;strong&gt;reflection&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item><item><title>5 Core Philosophies That Every Developer Should Have</title><link>https://berkselvi.dev/posts/5-core-philosophies-that-every-developer-should-have/</link><pubDate>Sun, 26 Sep 2021 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/posts/5-core-philosophies-that-every-developer-should-have/</guid><description>&lt;p&gt;I myself think that software is a philosophy of life before it is a job, and I take great pleasure in approaching most of the problems in my life as a software developer and solving them that way. In this article, I will talk about the cornerstones of this philosophy.&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Be a responsible person&lt;/li&gt;
&lt;li&gt;Don’t ruin anything in your code&lt;/li&gt;
&lt;li&gt;Don’t be a perfectionist&lt;/li&gt;
&lt;li&gt;Your asset is your knowledge&lt;/li&gt;
&lt;li&gt;Develop your community!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="be-a-responsible-person"&gt;Be a responsible person&lt;/h2&gt;
&lt;p&gt;As a programmer, if there is an error in the JavaScript code you write, you cannot say that the reason for this error is C++ compiler. This may sound a nonsense, but consider adapting this situation to yourself!&lt;/p&gt;</description><content>&lt;p&gt;I myself think that software is a philosophy of life before it is a job, and I take great pleasure in approaching most of the problems in my life as a software developer and solving them that way. In this article, I will talk about the cornerstones of this philosophy.&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Be a responsible person&lt;/li&gt;
&lt;li&gt;Don’t ruin anything in your code&lt;/li&gt;
&lt;li&gt;Don’t be a perfectionist&lt;/li&gt;
&lt;li&gt;Your asset is your knowledge&lt;/li&gt;
&lt;li&gt;Develop your community!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="be-a-responsible-person"&gt;Be a responsible person&lt;/h2&gt;
&lt;p&gt;As a programmer, if there is an error in the JavaScript code you write, you cannot say that the reason for this error is C++ compiler. This may sound a nonsense, but consider adapting this situation to yourself!&lt;/p&gt;
&lt;p&gt;Before you take a job, plan the requirements of that job well because that doesn’t happen when you’ve come a long way in the project and when you go to a client or your boss they ask you a valid question, “why didn’t you think of this before” If there’s a mistake and you’re going to explain it, be prepared for any questions It would be good because it can help you get out of such situations. But the answer you give should definitely not be something “that is impossible to do!”. You should try to offer different solution alternatives suitable for that problem.&lt;/p&gt;
&lt;h2 id="dont-ruin-anything-in-your-code"&gt;Don’t ruin anything in your code&lt;/h2&gt;
&lt;p&gt;Think of software like a building, if a window in an apartment is broken and not repaired, if another window is broken and no one cares, this will continue and eventually graffiti will appear on the walls and the building will start to go completely disorganized. Later; never live with a broken glass. This example of broken glass for software can be bad design, and a poor code that can lead to future bugs.&lt;/p&gt;
&lt;p&gt;Actually this situation is quite scientific, if you think about entropy in physics, disorder always wants to increase. It is up to you to not allow this. If there is a bug in the software, focus on fixing it before adding a new feature and keep going.Don’t say you can’t do it, say you did.&lt;/p&gt;
&lt;h2 id="dont-be-a-perfectionist"&gt;Don’t be a perfectionist&lt;/h2&gt;
&lt;p&gt;If you have a great idea, after a year or two of working on it and publishing it, it can be incredibly disappointing to learn from the feedback that it’s a really worthless idea. Before making flawless progress on any project, it makes much more sense to have an MVP with the core features of that project and release it, evaluate the feedback, and if all is well, keep moving forward.&lt;/p&gt;
&lt;p&gt;In short, perfectionism usually doesn’t work for software. It’s a better idea to be sure of the product first and then perfect it. As a result, the world is changing every day and new technologies and products are emerging every minute!&lt;/p&gt;
&lt;h2 id="your-asset-is-your-knowledge"&gt;Your asset is your knowledge&lt;/h2&gt;
&lt;p&gt;Your knowledge and experience is your most important asset. But these assets may be out of date, especially in the world of technology, everything is changing very quickly. The valuable assets you owned 1 year ago may now be worthless. That’s why you should focus on staying up to date.&lt;/p&gt;
&lt;p&gt;Managing a portfolio of knowledge and experience is like managing financial assets and has certain requirements;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Serious investors invest regularly as a habit: if you invest regularly, whether you make a little or a lot, you will get a huge profit in total in return.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Diversification is the key to long-term success: The more technology you are comfortable with, the better you can adapt to change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Smart investors balance their portfolios between low risk, low return and high risk, high return investments: Investing your entire asset (time and effort) in a high return asset that you think will be valuable in the future. Instead, branch your entity into secure branches and don’t put your entire entity in the same box.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Investors try to buy low and sell high for maximum returns: investing in assets that you think will appreciate in the future can yield big returns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Portfolios should be reviewed and rebalanced periodically: We do business in a highly volatile industry, so you should regularly review your investments and watch out for depreciating assets.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But the most important and indispensable of all these goals is to invest regularly. If you don’t invest, it doesn’t make any sense where you invest. Here are a few tools you can use as a tool to achieve these goals.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Learn at least one new language every year. Different languages ​​solve the same problems in different ways. Learning a few different approaches can help broaden your thinking and avoid getting stuck in a routine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read a technical book every quarter. Once you get used to it, read a book a month. Once you’ve mastered the technologies you’re currently using, branch out and work on some unrelated branches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read in non-technical books either. Computers are for humans, and if you can understand humans better, you can approach their solutions much better.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Join local user groups. Don’t just go and listen, participate actively. Isolation can be fatal to your career.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It doesn’t matter if you’ve used any of the technologies you’ll learn about in a project or even put it on your resume. Learning these will increase your learning ability and contribute to your problem solving ability.&lt;/p&gt;
&lt;p&gt;When you encounter a problem and you don’t know the answer to it, choose it as a goal and try to learn it. Ask a guru, if he isn’t around, find one online. This will both answer the problem and expand its network. Keep learning constantly. Carry and evaluate something to read in dead moments like traveling on the bus.&lt;/p&gt;
&lt;p&gt;Just as the product that a bookstore is marketing to you is not the best product it has, but a product it is only trying to sell, don’t rely on the content that comes first in search engines, think about its accuracy. and weigh it in your mind and never accept it without question.&lt;/p&gt;
&lt;h2 id="develop-your-community"&gt;Develop your community!&lt;/h2&gt;
&lt;p&gt;A good idea is an orphan when you don’t have effective communication. As developers, we use our communication skills when communicating with end users or creating documentation, communicating with our bosses or superiors. Here are a few ways to strengthen your communication skills;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Your job as a programmer is not to play word games, but to convey the information you want accurately and clearly. Plan what you want to say. Write a draft. Then ask yourself, “Does this encompass everything I’m trying to say?” Repeat until you meet this requirement. Make this plan not just when writing a document, but before meetings and speeches, even before meeting with clients you care about.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you can convey information, you can ensure correct communication. Therefore, you need to determine the level of knowledge that the other person can meet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are various ways of communication. Some customers may want to be informed by e-mail, while others may want to be called by phone.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want to be listened to by people, the first thing you will do is listen to them. If you can turn a meeting into a dialogue, there’s no better than you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Don’t make people wait by saying I will get back to you, I will answer as soon as possible.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thank you for reading! 🎉 In order not to miss my research in the field of software development, you can follow me at &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt;.&lt;/p&gt;</content></item></channel></rss>