<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[.NET Weekly Newsletter by Kanaiya Katarmal]]></title><description><![CDATA[Deep dives into .NET, C#, backend architecture, and cloud development. Practical insights, tools, and trends to help developers build scalable, high-performance applications.]]></description><link>https://newsletter.kanaiyakatarmal.com</link><image><url>https://substackcdn.com/image/fetch/$s_!8Ur6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F829af1f1-6666-4986-a0e6-bb94c5b4b3ca_256x256.png</url><title>.NET Weekly Newsletter by Kanaiya Katarmal</title><link>https://newsletter.kanaiyakatarmal.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 07 May 2026 11:10:02 GMT</lastBuildDate><atom:link href="https://newsletter.kanaiyakatarmal.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Kanaiya Katarmal]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[kanaiyakatarmal@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[kanaiyakatarmal@substack.com]]></itunes:email><itunes:name><![CDATA[Kanaiya Katarmal]]></itunes:name></itunes:owner><itunes:author><![CDATA[Kanaiya Katarmal]]></itunes:author><googleplay:owner><![CDATA[kanaiyakatarmal@substack.com]]></googleplay:owner><googleplay:email><![CDATA[kanaiyakatarmal@substack.com]]></googleplay:email><googleplay:author><![CDATA[Kanaiya Katarmal]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[.NET 10 Minimal APIs Just Leveled Up with Built-In Validation]]></title><description><![CDATA[Stop Writing Boilerplate: Automatic Request Validation Has Arrived]]></description><link>https://newsletter.kanaiyakatarmal.com/p/net-10-minimal-apis-just-leveled</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/net-10-minimal-apis-just-leveled</guid><pubDate>Thu, 23 Apr 2026 12:32:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Kaao!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><p>Minimal APIs have always been about simplicity but validation has been one of the pain points&#8230; until now.</p><p>With <strong>.NET 10</strong>, Microsoft introduces <strong>native request validation for Minimal APIs</strong>, eliminating the need for repetitive checks and bringing them closer to the robustness of traditional controllers.</p><p>Let&#8217;s break down what&#8217;s new, why it matters, and how to use it in real-world code.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Kaao!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Kaao!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png 424w, https://substackcdn.com/image/fetch/$s_!Kaao!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png 848w, https://substackcdn.com/image/fetch/$s_!Kaao!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png 1272w, https://substackcdn.com/image/fetch/$s_!Kaao!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Kaao!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png" width="1032" height="578" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:578,&quot;width&quot;:1032,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:54652,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/195231940?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Kaao!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png 424w, https://substackcdn.com/image/fetch/$s_!Kaao!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png 848w, https://substackcdn.com/image/fetch/$s_!Kaao!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png 1272w, https://substackcdn.com/image/fetch/$s_!Kaao!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fddfd420d-5692-471c-abf0-87d2764abf3a_1032x578.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h1> What&#8217;s New in .NET 10?</h1><p>In previous versions, validation in Minimal APIs required manual checks like:</p><pre><code>if (!MiniValidator.TryValidate(model, out var errors))
{
    return Results.ValidationProblem(errors);
}</code></pre><p>Or using external libraries.</p><p>&#128073; In <strong>.NET 10</strong>, validation is now:</p><ul><li><p> Built into the framework</p></li><li><p> Automatically executed before your endpoint logic</p></li><li><p> Based on standard <strong>Data Annotations</strong></p></li><li><p> Returning consistent error responses</p></li></ul><p></p><h1>Step 1: Enable Validation</h1><p>You only need <strong>one line</strong> to activate validation globally:</p><pre><code>var builder = WebApplication.CreateBuilder(args);

builder.Services.AddValidation(); // New in .NET 10

var app = builder.Build();</code></pre><p>That&#8217;s it. No filters. No middleware. No manual wiring.</p><div><hr></div><h1>Step 2: Define Your Request Model</h1><p>Use familiar <strong>Data Annotations</strong>:</p><pre><code>using System.ComponentModel.DataAnnotations;

public record CreateProductRequest(
    [Required] string Name,
    
    [Range(1, 10)] decimal Price,
    
    [Required] string Category
);</code></pre><p>Supported annotations include:</p><ul><li><p><code>[Required]</code><br></p></li><li><p><code>[Range]</code><br></p></li><li><p><code>[MinLength]</code><br></p></li><li><p><code>[MaxLength]</code><br></p></li><li><p><code>[EmailAddress]</code><br></p></li><li><p>and more...<br></p></li></ul><div><hr></div><h1>Step 3: Create a Minimal API Endpoint</h1><p>Now define your endpoint as usual:</p><pre><code>app.MapPost(&#8221;/products&#8221;, (CreateProductRequest request) =&gt;
{
    // This runs ONLY if validation passes
    return Results.Ok(new
    {
        Message = &#8220;Product created successfully&#8221;,
        Data = request
    });
});</code></pre><p>No validation logic required. Clean and focused.</p><div><hr></div><h1>What Happens on Invalid Requests?</h1><p>If validation fails, ASP.NET Core automatically returns a <strong>standardized response</strong>:</p><h3>Example Request:</h3><pre><code>{
  &#8220;name&#8221;: &#8220;Laptop&#8221;,
  &#8220;price&#8221;: 50,
  &#8220;category&#8221;: &#8220;&#8221;
}</code></pre><h3>Response:</h3><pre><code>{
  &#8220;title&#8221;: &#8220;One or more validation errors occurred.&#8221;,
  &#8220;errors&#8221;: {
    &#8220;Price&#8221;: [
      &#8220;The field Price must be between 1 and 10.&#8221;
    ],
    &#8220;Category&#8221;: [
      &#8220;The Category field is required.&#8221;
    ]
  }
}</code></pre><p>&#128073; Your endpoint is <strong>never executed</strong>.</p><div><hr></div><h1>Full Working Example</h1><p>Here&#8217;s a complete <code>Program.cs</code>:</p><pre><code>using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder(args);

// Enable validation
builder.Services.AddValidation();

var app = builder.Build();

// Request model
public record CreateProductRequest(
    [Required] string Name,
    [Range(1, 10)] decimal Price,
    [Required] string Category
);

// Endpoint
app.MapPost(&#8221;/products&#8221;, (CreateProductRequest request) =&gt;
{
    return Results.Ok(new
    {
        Message = &#8220;Product created successfully&#8221;,
        Product = request
    });
});

app.Run();</code></pre><h1>Minimal APIs vs Controllers </h1><p>For a long time, controllers had a clear advantage: <strong>built-in validation</strong>.</p><p>That gap is now gone.</p><div><hr></div><h2>Automatic Validation</h2><p><strong>Before .NET 10</strong><br>&#10060; Not available in Minimal APIs<br>You had to manually validate every request or use external libraries.</p><p><strong>With .NET 10</strong><br>&#9989; Built-in and automatic<br>Validation runs before your endpoint logic executes.</p><div><hr></div><h2>Clean Endpoints</h2><p><strong>Before .NET 10</strong><br>&#9888;&#65039; Often cluttered with validation checks<br>Your business logic was mixed with validation code.</p><p><strong>With .NET 10</strong><br>&#9989; Clean and focused<br>Endpoints now contain only business logic &#8212; no extra noise.</p><div><hr></div><h2>Consistency</h2><p><strong>Before .NET 10</strong><br>&#10060; Inconsistent validation responses<br>Different endpoints could return different error formats.</p><p><strong>With .NET 10</strong><br>&#9989; Standardized responses<br>All validation errors follow a unified structure automatically.</p><h1>Pro Tips</h1><ul><li><p>Combine with <strong>FluentValidation</strong> for advanced scenarios</p></li><li><p>Keep request models small and focused</p></li><li><p>Use custom attributes for reusable rules</p></li><li><p>Log validation failures for debugging insights</p></li></ul><p></p><h1>Final Thoughts</h1><p>.NET 10 doesn&#8217;t just improve Minimal APIs &#8212;<br>it removes one of their biggest limitations.</p><p>You now get:</p><ul><li><p>Clean code</p></li><li><p>Built-in validation</p></li><li><p>Consistent behavior</p></li><li><p>Less mental overhead</p></li></ul><p>All with <strong>one line of configuration</strong>.</p><h1>What Do You Think?</h1><p>Have you tried this feature yet?<br>Do you still prefer controllers, or are Minimal APIs now your go-to?</p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How to Secure Your .NET Dependencies Using Dependabot (No CI Required)]]></title><description><![CDATA[Most .NET developers already use GitHub for their projects.]]></description><link>https://newsletter.kanaiyakatarmal.com/p/how-to-secure-your-net-dependencies</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/how-to-secure-your-net-dependencies</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Thu, 26 Mar 2026 04:31:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!dARz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dARz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dARz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png 424w, https://substackcdn.com/image/fetch/$s_!dARz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png 848w, https://substackcdn.com/image/fetch/$s_!dARz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png 1272w, https://substackcdn.com/image/fetch/$s_!dARz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dARz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png" width="1028" height="578" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:578,&quot;width&quot;:1028,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51141,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/191975264?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dARz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png 424w, https://substackcdn.com/image/fetch/$s_!dARz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png 848w, https://substackcdn.com/image/fetch/$s_!dARz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png 1272w, https://substackcdn.com/image/fetch/$s_!dARz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd583bde2-ef93-440b-b77e-696aedb8cbfe_1028x578.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Most .NET developers already use GitHub for their projects.</p><p>What&#8217;s less obvious is that GitHub can also help you manage dependency security with almost no effort. If you&#8217;re using NuGet packages&#8212;and you probably are&#8212;this matters more than you think.</p><p>A single vulnerable dependency is enough to expose your entire application. And in many cases, you won&#8217;t even realize it&#8217;s there.</p><p>The good news is that GitHub provides a built-in solution: Dependabot.</p><h2>The Problem with NuGet Dependencies</h2><p>Adding a package is simple:</p><pre><code>dotnet add package System.Data.SqlClient --version 4.8.4</code></pre><p>The application builds. Everything works as expected.</p><p>But there&#8217;s a hidden risk. That version might already have a known vulnerability (a CVE). Unless you actively check, you won&#8217;t know. And by the time it&#8217;s discovered, it could already be in production.</p><p>This is a common pattern:</p><ul><li><p>Packages become outdated quietly<br></p></li><li><p>Vulnerabilities are disclosed later<br></p></li><li><p>Applications continue running with known risks<br></p></li></ul><p>Dependency issues are rarely obvious during development, which is what makes them dangerous.</p><div><hr></div><h2>What Dependabot Actually Does</h2><p>Dependabot is GitHub&#8217;s built-in dependency monitoring tool. Once enabled, it works in the background and takes care of several things automatically:</p><ul><li><p>Scans your project dependencies<br></p></li><li><p>Detects known vulnerabilities<br></p></li><li><p>Notifies you through security alerts<br></p></li><li><p>Creates pull requests with safe updates<br></p></li></ul><p>It&#8217;s not a replacement for good security practices, but it removes a lot of the manual work.</p><div><hr></div><h2>Step 1: Enable Dependabot</h2><p>In your GitHub repository, go to:</p><p><strong>Settings &#8594; Security &#8594; Code security and analysis</strong></p><p>Enable the following:</p><ul><li><p>Dependency graph<br></p></li><li><p>Dependabot alerts<br></p></li><li><p>Dependabot security updates<br></p></li></ul><p>Once these are turned on, GitHub starts analyzing your dependencies automatically.</p><div><hr></div><h2>Step 2: Let GitHub Do the Scanning</h2><p>After enabling these features, GitHub will scan your <code>.csproj</code> files and identify vulnerable packages.</p><p>If a vulnerability is found, you&#8217;ll see an alert like this:</p><pre><code>System.Data.SqlClient 4.8.4 &#8594; High severity vulnerability</code></pre><p>You don&#8217;t need to run any tools manually. The scanning happens continuously in the background.</p><div><hr></div><h2>Step 3: Configure Automatic Updates</h2><p>To make Dependabot proactive, add a configuration file to your repository:</p><pre><code>.github/dependabot.yml</code></pre><p>Example:</p><pre><code>version: 2
updates:
  - package-ecosystem: &#8220;nuget&#8221;
    directory: &#8220;/&#8221;
    schedule:
      interval: &#8220;daily&#8221;</code></pre><p>This tells GitHub to check for dependency updates every day.</p><div><hr></div><h2>Step 4: How Dependabot Fixes Issues</h2><p>When a vulnerability is detected and a fix is available, Dependabot will open a pull request automatically.</p><p>A typical PR looks like this:</p><p><strong>Title:</strong></p><pre><code>Bump System.Data.SqlClient from 4.8.4 to 4.8.5</code></pre><p><strong>Description:</strong></p><pre><code>Fixes CVE-2024-0056

This update resolves a high severity vulnerability.</code></pre><p>At this point, the process is straightforward. You review the change, run your tests, and merge.</p><div><hr></div><h2>Step 5: Optional Local Verification</h2><p>If you want to double-check locally, you can still run:</p><pre><code>dotnet list package --vulnerable</code></pre><p>This is useful during development, but once Dependabot is enabled, it&#8217;s no longer something you need to rely on.</p><div><hr></div><h2>Good Practices to Follow</h2><p>Dependabot works best when combined with a few simple habits:</p><ul><li><p>Keep updates frequent rather than batching them<br></p></li><li><p>Review and merge security PRs promptly<br></p></li><li><p>Avoid packages that are no longer maintained<br></p></li><li><p>Be aware of transitive dependencies (packages you don&#8217;t directly install)<br></p></li></ul><p>These small steps significantly reduce your risk over time.</p><div><hr></div><h2>Where Tools Like Depadbot Fit</h2><p>Dependabot solves the basics well: detection and automated updates.</p><p>Tools like Depadbot build on top of that by providing deeper analysis, better visibility into dependency chains, and more context around risk.</p><p>If Dependabot helps you react to vulnerabilities, tools like Depadbot help you understand and prioritize them.</p><div><hr></div><h2>Final Thoughts</h2><p>Dependency security doesn&#8217;t need to be complicated.</p><p>You don&#8217;t need a full security pipeline to get started. Enabling Dependabot takes a few minutes, and from that point on, your repository is continuously monitored.</p><p>It&#8217;s a small change, but it closes a gap that many teams overlook.</p><div><hr></div><h2>One thing to remember</h2><p>If Dependabot isn&#8217;t enabled, your dependencies are aging silently and you probably won&#8217;t notice until it&#8217;s too late.</p><div><hr></div><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279; <a href="https://github.com/KanaiyaKatarmal/githuazureterraform">https://github.com/KanaiyaKatarmal/githuazureterraform</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Server-Sent Events in ASP.NET Core: Real-Time Updates Without WebSockets]]></title><description><![CDATA[Real-time features have become a standard requirement in modern web apps.]]></description><link>https://newsletter.kanaiyakatarmal.com/p/server-sent-events-in-aspnet-core</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/server-sent-events-in-aspnet-core</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Mon, 16 Mar 2026 04:31:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zISN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><p>Most developers immediately reach for <strong>WebSockets or SignalR</strong>, but in many cases that is unnecessary.</p><p>There is a much simpler solution built directly into the browser:</p><p><strong>Server-Sent Events (SSE).</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zISN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zISN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png 424w, https://substackcdn.com/image/fetch/$s_!zISN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png 848w, https://substackcdn.com/image/fetch/$s_!zISN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png 1272w, https://substackcdn.com/image/fetch/$s_!zISN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zISN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png" width="734" height="395" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:395,&quot;width&quot;:734,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:106042,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/189983824?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zISN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png 424w, https://substackcdn.com/image/fetch/$s_!zISN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png 848w, https://substackcdn.com/image/fetch/$s_!zISN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png 1272w, https://substackcdn.com/image/fetch/$s_!zISN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3c30723-ac59-40b3-8b58-10feb8c6f619_734x395.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>SSE allows the server to <strong>push updates to the browser over a simple HTTP connection</strong>.</p><p>And starting with <strong>modern ASP.NET Core</strong>, SSE support is now <strong>first-class with </strong><code>Results.ServerSentEvents()</code>.</p><p>Let&#8217;s explore how it works.</p><div><hr></div><h1>What Are Server-Sent Events?</h1><p>Server-Sent Events are a <strong>one-way streaming protocol</strong> where:</p><ul><li><p>The <strong>client opens a connection</strong></p></li><li><p>The <strong>server keeps the HTTP connection open</strong></p></li><li><p>The <strong>server continuously pushes messages</strong></p></li></ul><p>The browser receives these messages using the <strong>EventSource API</strong>.</p><p>Key characteristics:</p><ul><li><p>Built on <strong>standard HTTP</strong></p></li><li><p><strong>Automatic reconnection</strong></p></li><li><p>Supports <strong>custom events</strong></p></li><li><p>Lightweight compared to WebSockets</p></li></ul><p>Architecture:</p><pre><code>Browser (EventSource)
        &#9474;
        &#9474; HTTP GET /progress
        &#9660;
ASP.NET Core SSE endpoint
        &#9474;
        &#9474; Stream events
        &#9660;
Browser receives messages in real time</code></pre><div><hr></div><h1>Basic SSE Example in ASP.NET Core</h1><p>Let&#8217;s build a simple <strong>job progress stream</strong>.</p><h2>Server Setup</h2><pre><code>using System.Net.ServerSentEvents;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/progress", () =&gt;
{
    async IAsyncEnumerable&lt;SseItem&lt;string&gt;&gt; Stream()
    {
        for (int i = 1; i &lt;= 10; i++)
        {
            yield return new SseItem&lt;string&gt;(
                data: $"{i} completed"
            );

            await Task.Delay(1000);
        }

        yield return new SseItem&lt;string&gt;(
            data: "Job Finished &#9989;",
            eventType: &#8220;completed&#8221;
        );
    }

    return Results.ServerSentEvents(Stream());
});

app.Run();</code></pre><p>Key idea:</p><p>Instead of returning a normal response, we return an <strong>async stream of events</strong>.</p><p>ASP.NET Core automatically converts each item into the <strong>SSE wire format</strong>:</p><pre><code>data: Step 1 completed

data: Step 2 completed

event: completed
data: Job Finished</code></pre><div><hr></div><h1>Client Side: Listening to Events</h1><p>Browsers support SSE through the <strong>EventSource API</strong>.</p><p>Example:</p><pre><code>&lt;script&gt;
const evt = new EventSource("/progress");

// default messages
evt.onmessage = function(e) {
    console.log(e.data);
};

// custom event
evt.addEventListener("completed", function (e) {
    console.log("Job done:", e.data);
    evt.close();
});
&lt;/script&gt;</code></pre><p>When the server sends:</p><pre><code>data: Step 3 completed</code></pre><p>The browser triggers:</p><pre><code>evt.onmessage</code></pre><p>When the server sends:</p><pre><code>event: completed
data: Job Finished</code></pre><p>The browser triggers the <strong>custom event listener</strong>.</p><div><hr></div><h1>Full Working Example</h1><p>Your HTML page could look like this:</p><pre><code>&lt;h2&gt;Live Job Progress&lt;/h2&gt;
&lt;div id="log"&gt;&lt;/div&gt;

&lt;script&gt;
const evt = new EventSource("/progress");

evt.onmessage = function(e) {
    document.getElementById("log").innerHTML +=
        "&lt;p&gt;" + e.data + "&lt;/p&gt;";
};

evt.addEventListener("completed", function (e) {
    document.getElementById("log").innerHTML +=
        "&lt;p&gt;&lt;strong&gt;" + e.data + "&lt;/strong&gt;&lt;/p&gt;";

    evt.close();
});
&lt;/script&gt;</code></pre><p>The result is a <strong>live updating progress log</strong> without refreshing the page.</p><div><hr></div><h1>Why Use SSE Instead of WebSockets?</h1><p>SSE shines when you only need <strong>server &#8594; client streaming</strong>.</p><p>Use SSE when you need:</p><ul><li><p>progress updates</p></li><li><p>streaming logs</p></li><li><p>AI token streaming</p></li><li><p>notifications</p></li><li><p>dashboard updates</p></li></ul><p>Use WebSockets when you need <strong>two-way communication</strong>.</p><div><hr></div><h1>Advantages of SSE in ASP.NET Core</h1><p>Modern ASP.NET Core makes SSE especially powerful because it integrates with:</p><ul><li><p><code>IAsyncEnumerable</code></p></li><li><p><strong>Minimal APIs</strong></p></li><li><p><strong>Streaming responses</strong></p></li></ul><p>This means you can connect SSE directly to:</p><ul><li><p>database change streams</p></li><li><p>background workers</p></li><li><p>message queues</p></li><li><p>AI token streams</p></li></ul><p>Example pattern:</p><pre><code>Queue &#8594; Background Worker &#8594; SSE Stream &#8594; Browser</code></pre><div><hr></div><h1>Production Tips</h1><h3>1. Disable response buffering</h3><p>Some proxies buffer responses.</p><p>For Nginx:</p><pre><code>proxy_buffering off;</code></pre><div><hr></div><h3>2. Use keep-alive messages</h3><p>If the connection stays silent too long, some proxies close it.</p><p>Send periodic heartbeat events.</p><div><hr></div><h3>3. Handle reconnects</h3><p>Browsers automatically reconnect if the connection drops.</p><p>You can support <strong>Last-Event-ID</strong> for reliable resume.</p><div><hr></div><h1>When SSE Is Perfect</h1><p>SSE is ideal for:</p><ul><li><p><strong>AI streaming responses</strong></p></li><li><p><strong>Live job progress</strong></p></li><li><p><strong>Dev logs</strong></p></li><li><p><strong>Real-time dashboards</strong></p></li><li><p><strong>Notifications</strong></p></li></ul><p>And because it runs over <strong>plain HTTP</strong>, it works beautifully behind load balancers and CDNs.</p><div><hr></div><h1>Final Thoughts</h1><p>Server-Sent Events are one of the <strong>most underrated features in modern web development</strong>.</p><p>With ASP.NET Core&#8217;s new <code>Results.ServerSentEvents()</code><strong> API</strong>, building real-time experiences is now incredibly simple.</p><p>In many cases, you can replace complex WebSocket infrastructure with just:</p><pre><code>IAsyncEnumerable + ServerSentEvents</code></pre><p>Sometimes the best real-time solution is also the <strong>simplest one</strong>.</p><div><hr></div><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/ServerSentEvents">https://sourcecode.kanaiyakatarmal.com/ServerSentEvents</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The New .NET SLNX Solution File]]></title><description><![CDATA[Migrate to .NET's New .slnx Solution Format]]></description><link>https://newsletter.kanaiyakatarmal.com/p/the-new-net-slnx-solution-file</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/the-new-net-slnx-solution-file</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Mon, 09 Mar 2026 04:31:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-rlx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For years, the classic Visual Studio <code>.sln</code> file has been the backbone of .NET solutions. It worked &#8212; but it wasn&#8217;t exactly elegant. The format was proprietary, loosely structured, and notoriously difficult to diff or merge in version control.</p><p>With the introduction of the new <strong>XML-based </strong><code>.slnx</code><strong> solution format</strong>, Microsoft modernized solution files to align with the SDK-style project system and the broader .NET ecosystem.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-rlx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-rlx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png 424w, https://substackcdn.com/image/fetch/$s_!-rlx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png 848w, https://substackcdn.com/image/fetch/$s_!-rlx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png 1272w, https://substackcdn.com/image/fetch/$s_!-rlx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-rlx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png" width="1092" height="613" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:613,&quot;width&quot;:1092,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56660,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/189688398?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-rlx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png 424w, https://substackcdn.com/image/fetch/$s_!-rlx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png 848w, https://substackcdn.com/image/fetch/$s_!-rlx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png 1272w, https://substackcdn.com/image/fetch/$s_!-rlx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718474f7-e983-4b42-852f-a0204c240ac7_1092x613.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you&#8217;re maintaining enterprise .NET systems, building modular architectures, or simply trying to reduce Git merge conflicts, migrating to <code>.slnx</code> is a meaningful upgrade.</p><p>This article walks you through:</p><ul><li><p>Why <code>.slnx</code> exists</p></li><li><p>What changes technically</p></li><li><p>Step-by-step migration process</p></li><li><p>CI/CD considerations</p></li><li><p>Common pitfalls</p></li><li><p>Enterprise rollout strategy</p></li></ul><h1>Why Move to <code>.slnx</code>?</h1><h3>The Old <code>.sln</code> Format</h3><p>The legacy <code>.sln</code> format:</p><ul><li><p>Is not XML</p></li><li><p>Is hard to diff</p></li><li><p>Is difficult to merge</p></li><li><p>Has limited tooling extensibility</p></li><li><p>Contains GUID-heavy project references</p></li><li><p>Was designed long before SDK-style projects</p></li></ul><h3>The New <code>.slnx</code> Format</h3><p>The <code>.slnx</code> format:</p><ul><li><p>Is XML-based</p></li><li><p>Uses structured elements</p></li><li><p>Improves merge friendliness</p></li><li><p>Aligns with modern MSBuild principles</p></li><li><p>Reduces legacy solution cruft</p></li><li><p>Plays better with automation</p></li></ul><p>In short: <code>.slnx</code><strong> makes solution files first-class citizens in the modern .NET ecosystem.</strong></p><div><hr></div><h1>What Actually Changes?</h1><p>Here&#8217;s a conceptual comparison.</p><h3>Classic <code>.sln</code></h3><pre><code>Project("{GUID}") = "MyApp", "MyApp.csproj", "{PROJECT-GUID}"
EndProject</code></pre><h3>XML <code>.slnx</code></h3><pre><code>&lt;Solution&gt;
  &lt;Project Path="MyApp/MyApp.csproj" /&gt;
&lt;/Solution&gt;</code></pre><p>The structure becomes:</p><ul><li><p>Declarative</p></li><li><p>Clean</p></li><li><p>Human-readable</p></li><li><p>Scriptable</p></li></ul><div><hr></div><h1>Step-by-Step Migration Guide</h1><h2>Step 1: Ensure Your Environment Supports <code>.slnx</code></h2><p>You need:</p><ul><li><p>Latest Visual Studio (Preview or stable supporting <code>.slnx</code>)</p></li><li><p>Latest .NET SDK</p></li><li><p>MSBuild updated</p></li></ul><p>Check your SDK version:</p><pre><code>dotnet --version</code></pre><p>Upgrade if necessary.</p><div><hr></div><h2>Step 2: Convert the Solution</h2><p>Microsoft provides a built-in conversion mechanism.</p><h3>Option A &#8211; Using CLI</h3><pre><code>dotnet sln MySolution.sln migrate</code></pre><p>Or:</p><pre><code>dotnet new sln --format slnx</code></pre><h3>Option B &#8211; Using Visual Studio</h3><ol><li><p>Open existing <code>.sln</code></p></li><li><p>Right-click Solution</p></li><li><p>Choose <strong>Save As</strong></p></li><li><p>Select <code>.slnx</code></p></li></ol><div><hr></div><h2>Step 3: Validate the XML</h2><p>Open the new <code>.slnx</code>.</p><p>You should see something like:</p><pre><code>&lt;Solution&gt;
  &lt;Project Path="src/App/App.csproj" /&gt;
  &lt;Project Path="src/Library/Library.csproj" /&gt;
&lt;/Solution&gt;</code></pre><p>Validate:</p><ul><li><p>All projects are included</p></li><li><p>Paths are correct</p></li><li><p>No legacy GUID entries</p></li></ul><h1>Rollback Plan</h1><p>If something fails:</p><ol><li><p>Keep original <code>.sln</code></p></li><li><p>Switch CI back</p></li><li><p>Revert branch</p></li><li><p>Document incompatibility</p></li></ol><p>Migration should always be reversible in early stages.</p><h1>Final Recommendations</h1><p>&#10004; Test in a feature branch<br>&#10004; Upgrade SDK everywhere first<br>&#10004; Migrate small solutions first<br>&#10004; Communicate clearly to your team<br>&#10004; Remove <code>.sln</code> only after validation</p><h1>Conclusion</h1><p>The move from <code>.sln</code> to <code>.slnx</code> represents more than a format change.</p><p>It signals:</p><ul><li><p>Alignment with modern .NET architecture</p></li><li><p>DevOps maturity</p></li><li><p>Cleaner source control workflows</p></li><li><p>Better automation potential</p></li></ul><p>For greenfield projects, <code>.slnx</code> is the obvious choice.</p><p>For legacy systems, migration should be planned but it&#8217;s absolutely worth considering as part of modernization efforts.</p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[EF Core Audit Interceptor]]></title><description><![CDATA[Automatic CreatedOn/UpdatedOn Timestamps Without Touching Controllers]]></description><link>https://newsletter.kanaiyakatarmal.com/p/ef-core-audit-interceptor</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/ef-core-audit-interceptor</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Tue, 03 Mar 2026 04:31:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!H_cJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;ve ever set CreatedOn and UpdatedOn in every controller action, you already know it becomes repetitive fast.<br>This project shows a cleaner pattern: centralize auditing in EF Core with a SaveChangesInterceptor so inserts and updates are stamped automatically.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!H_cJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!H_cJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png 424w, https://substackcdn.com/image/fetch/$s_!H_cJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png 848w, https://substackcdn.com/image/fetch/$s_!H_cJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png 1272w, https://substackcdn.com/image/fetch/$s_!H_cJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!H_cJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png" width="724" height="409.36139066788655" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:618,&quot;width&quot;:1093,&quot;resizeWidth&quot;:724,&quot;bytes&quot;:59749,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/189671120?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!H_cJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png 424w, https://substackcdn.com/image/fetch/$s_!H_cJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png 848w, https://substackcdn.com/image/fetch/$s_!H_cJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png 1272w, https://substackcdn.com/image/fetch/$s_!H_cJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F211a007d-3405-4acb-a40b-63db3ae95f29_1093x618.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this post, I&#8217;ll walk through a minimal ASP.NET Core Web API + SQLite example that does exactly that</p><h2><strong>Why this pattern matters</strong></h2><p>Most teams start with &#8220;just set timestamps in the API layer.&#8221; That works until:</p><ul><li><p>You forget to set a value in one endpoint</p></li><li><p>You add background jobs that bypass controllers</p></li><li><p>You duplicate the same logic across services and handlers</p></li><li><p>You accidentally allow clients to overwrite system fields</p></li></ul><p>A SaveChangesInterceptor solves this at the persistence boundary.<br>If an entity is tracked and saved, audit values are applied consistently.</p><p></p><h2><strong>What this demo app contains</strong></h2><p>Your EFCoreAuditInterceptor sample is intentionally focused:</p><ul><li><p>ASP.NET Core Web API</p></li><li><p>EF Core + SQLite</p></li><li><p>One entity: Customer</p></li><li><p>One base type: AuditableEntity</p></li><li><p>One interceptor: AuditInterceptor</p></li><li><p>CRUD controller with no manual audit assignments</p></li></ul><p>That keeps the concept sharp and easy to reuse.</p><p></p><h2><strong>Step 1: Define a shared auditable base class</strong></h2><p>All entities that need auditing inherit from a base type.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;af17a4a7-eded-41f0-ab5b-f9eb069316d5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">namespace EFCoreAuditInterceptor.Data
{
    public abstract class AuditableEntity
    {
        public DateTime CreatedOn { get; set; }
        public DateTime? UpdatedOn { get; set; }
    }
}</code></pre></div><p>Why nullable UpdatedOn?<br>A new row may not have been updated yet, so null communicates that state clearly.</p><h2><strong>Step 2: Make your entity inherit it</strong></h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;bdec9b8f-d43b-4d2b-85e4-9d73d9642777&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">namespace EFCoreAuditInterceptor.Data
{
    public class Customer : AuditableEntity
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public bool IsDeleted { get; set; }
    }
}</code></pre></div><p>Now Customer automatically participates in auditing logic.</p><h2><strong>Step 3: Implement the SaveChangesInterceptor</strong></h2><p>This is the core of the pattern.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;0b7bc104-0f05-45b3-8429-432916633277&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace EFCoreAuditInterceptor.Data
{
    public class AuditInterceptor : SaveChangesInterceptor
    {
        public override InterceptionResult&lt;int&gt; SavingChanges(
            DbContextEventData eventData,
            InterceptionResult&lt;int&gt; result)
        {
            ApplyAuditInfo(eventData.Context);
            return base.SavingChanges(eventData, result);
        }

        public override ValueTask&lt;InterceptionResult&lt;int&gt;&gt; SavingChangesAsync(
            DbContextEventData eventData,
            InterceptionResult&lt;int&gt; result,
            CancellationToken cancellationToken = default)
        {
            ApplyAuditInfo(eventData.Context);
            return base.SavingChangesAsync(eventData, result, cancellationToken);
        }

        private static void ApplyAuditInfo(DbContext? context)
        {
            if (context == null)
                return;

            foreach (var entry in context.ChangeTracker.Entries&lt;AuditableEntity&gt;())
            {
                if (entry.State == EntityState.Added)
                {
                    entry.Entity.CreatedOn = DateTime.UtcNow;
                }

                if (entry.State == EntityState.Modified)
                {
                    entry.Entity.UpdatedOn = DateTime.UtcNow;

                    // Prevent accidental overwrite of original CreatedOn value
                    entry.Property(x =&gt; x.CreatedOn).IsModified = false;
                }
            }
        }
    }
}</code></pre></div><p>Important behavior:</p><ul><li><p>Added entities get CreatedOn</p></li><li><p>Modified entities get UpdatedOn</p></li><li><p>CreatedOn is protected during updates</p></li></ul><h2><strong>Step 4: Register interceptor once in DI/DbContext pipeline</strong></h2><p>Your app already wires this via AddDbContext options (great).<br>Recommendation: keep that single registration path and avoid adding it again in OnConfiguring.</p><p>Program setup pattern:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;1b4cee02-fefb-456d-90ee-48312510649c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">builder.Services.AddScoped&lt;AuditInterceptor&gt;();

builder.Services.AddDbContext&lt;AppDbContext&gt;((sp, options) =&gt;
{
    options.UseSqlite("Data Source=EFCoreAuditInterceptor.db");
    options.AddInterceptors(sp.GetRequiredService&lt;AuditInterceptor&gt;());
});</code></pre></div><p>And DbContext stays lean:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;75ed86fa-df2c-4b93-9380-e8f1202249b1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions&lt;AppDbContext&gt; options) : base(options) { }

    public DbSet&lt;Customer&gt; Customers =&gt; Set&lt;Customer&gt;();
}</code></pre></div><h2><strong>Step 5: Keep controllers clean (no audit code)</strong></h2><p>Your controller already does this correctly: create/update customer values, call SaveChangesAsync, done.</p><p>Create action style:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;54397b26-de51-46b4-ba8f-5314e147e618&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">var customer = new Customer
{
    Name = request.Name,
    IsDeleted = request.IsDeleted
};

_context.Customers.Add(customer);
await _context.SaveChangesAsync();</code></pre></div><p>No CreatedOn/UpdatedOn assignment is needed anywhere in API logic.</p><p> <strong>End-to-end behavior</strong></p><h3>Create customer</h3><p>Request:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;7bfb182f-1d05-4b3c-b26e-b4f3f65431b3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">POST /api/customers
{
  "name": "Alice",
  "isDeleted": false
}</code></pre></div><p>Response includes auto-set CreatedOn:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;ffd5196a-7354-42f6-8825-cc9379830678&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{
  "id": 1,
  "name": "Alice",
  "isDeleted": false,
  "createdOn": "2026-03-02T10:15:21.123Z",
  "updatedOn": null
}</code></pre></div><h3>Update customer</h3><p>Request:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;49121fe7-ff4b-477e-a89b-dc1e8a5db474&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">PUT /api/customers/1
{
  "name": "Alice Cooper",
  "isDeleted": false
}</code></pre></div><p>Response shows UpdatedOn stamped automatically while CreatedOn stays unchanged.</p><h2><strong>One demo note in your current project</strong></h2><p>You currently recreate the database on app startup (EnsureDeleted + EnsureCreated).<br>That is perfect for demos, but in real apps switch to EF Core migrations so data persists across runs.</p><h2><strong>Why this scales well</strong></h2><p>This approach stays maintainable because:</p><ul><li><p>Audit rules are centralized in one place</p></li><li><p>Every save path is covered (controllers, services, jobs)</p></li><li><p>API contracts remain focused on business data</p></li><li><p>You reduce bugs from inconsistent timestamp handling</p></li></ul><p>As your model grows, each new auditable entity simply inherits AuditableEntity.</p><p></p><h2><strong>Production hardening ideas (next steps)</strong></h2><p>If you evolve this demo into a production baseline, consider adding:</p><ul><li><p>CreatedBy and UpdatedBy (from authenticated user context)</p></li><li><p>Soft-delete timestamps (DeletedOn, DeletedBy)</p></li><li><p>Multi-tenant audit context</p></li><li><p>UTC normalization tests around interceptor behavior</p></li><li><p>Database defaults as a fallback safety net</p></li></ul><p></p><h2>Summary</h2><ul><li><p>This project demonstrates a clean auditing pattern where EF Core automatically stamps <a href="vscode-file://vscode-app/c:/Users/Bhanushali/AppData/Local/Programs/Microsoft%20VS%20Code/072586267e/resources/app/out/vs/code/electron-browser/workbench/workbench.html">CreatedOn</a> and <a href="vscode-file://vscode-app/c:/Users/Bhanushali/AppData/Local/Programs/Microsoft%20VS%20Code/072586267e/resources/app/out/vs/code/electron-browser/workbench/workbench.html">UpdatedOn</a> at save time.</p></li><li><p>The key idea is centralizing audit logic in a <a href="vscode-file://vscode-app/c:/Users/Bhanushali/AppData/Local/Programs/Microsoft%20VS%20Code/072586267e/resources/app/out/vs/code/electron-browser/workbench/workbench.html">SaveChangesInterceptor</a>, so application code stays simple and consistent.</p></li><li><p>Entities inherit from a shared auditable base type, and the interceptor applies rules based on entity state (<a href="vscode-file://vscode-app/c:/Users/Bhanushali/AppData/Local/Programs/Microsoft%20VS%20Code/072586267e/resources/app/out/vs/code/electron-browser/workbench/workbench.html">Added</a> / <a href="vscode-file://vscode-app/c:/Users/Bhanushali/AppData/Local/Programs/Microsoft%20VS%20Code/072586267e/resources/app/out/vs/code/electron-browser/workbench/workbench.html">Modified</a>).</p></li><li><p>This reduces duplication, prevents missed timestamp updates, and scales well as the domain grows.</p></li><li><p>For production, keep single interceptor registration, use migrations (not recreate DB), and consider adding user-based audit fields.</p><p></p></li></ul><p> <strong>Question for you:</strong> Which one do you find yourself using most often in your C# projects, and why?</p><div><hr></div><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/EFCoreAuditInterceptor">https://sourcecode.kanaiyakatarmal.com/EFCoreAuditInterceptor</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Top 10 Mistakes in REST API Design (And How to Avoid Them)]]></title><description><![CDATA[Common REST API Design Mistakes That Hurt Scalability, Security, and Developer Experience]]></description><link>https://newsletter.kanaiyakatarmal.com/p/top-10-mistakes-in-rest-api-design</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/top-10-mistakes-in-rest-api-design</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Mon, 09 Feb 2026 04:31:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4zrj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>APIs are the backbone of modern applications.</p><p>But here&#8217;s the uncomfortable truth: most APIs are technically functional&#8230; and architecturally flawed.</p><p>After reviewing common patterns highlighted in <em>Top 10 Mistakes in REST API Design</em>, I realized something important:</p><p>&#128073; Most API problems aren&#8217;t about code.<br>&#128073; They&#8217;re about design decisions.</p><p>Let&#8217;s break down the 10 most common REST API mistakes and what to do instead.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4zrj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4zrj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png 424w, https://substackcdn.com/image/fetch/$s_!4zrj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png 848w, https://substackcdn.com/image/fetch/$s_!4zrj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png 1272w, https://substackcdn.com/image/fetch/$s_!4zrj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4zrj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png" width="1076" height="602" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:602,&quot;width&quot;:1076,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56458,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/176573094?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4zrj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png 424w, https://substackcdn.com/image/fetch/$s_!4zrj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png 848w, https://substackcdn.com/image/fetch/$s_!4zrj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png 1272w, https://substackcdn.com/image/fetch/$s_!4zrj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67f152aa-2e07-447b-8604-4488ec3b8b5b_1076x602.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>1. Using Verbs in Endpoint URLs</h2><p><strong>&#10060; Mistake:</strong></p><pre><code><code>/createUser
/deleteOrder/:id
</code></code></pre><p>This mixes actions into URLs.</p><p><strong>&#9989; Best Practice:</strong><br>Use nouns. Let HTTP methods define the action.</p><pre><code><code>POST   /users        &#8594; create user
DELETE /orders/{id}  &#8594; delete order
</code></code></pre><p>Your endpoints should represent <em>resources</em>, not actions.</p><div><hr></div><h2>2. Ignoring Proper Use of HTTP Methods</h2><p>Each HTTP method has a semantic meaning:</p><ul><li><p><code>GET</code> &#8594; Read</p></li><li><p><code>POST</code> &#8594; Create</p></li><li><p><code>PUT/PATCH</code> &#8594; Update</p></li><li><p><code>DELETE</code> &#8594; Remove</p></li></ul><p><strong>&#10060; Mistake:</strong><br>Using <code>GET</code> to delete something or <code>POST</code> for idempotent updates.</p><p>This breaks expectations and can cause serious caching or security issues.</p><div><hr></div><h2>3. Overloading 200 OK</h2><p>Returning <code>200 OK</code> for everything &#8212; even errors &#8212; is one of the most common API design sins.</p><p><strong>&#10060; Example:</strong></p><pre><code><code>200 OK
{
  "error": "User not found"
}
</code></code></pre><p><strong>&#9989; Use proper status codes:</strong></p><ul><li><p><code>201</code> &#8594; Created</p></li><li><p><code>400</code> &#8594; Bad Request</p></li><li><p><code>404</code> &#8594; Not Found</p></li><li><p><code>409</code> &#8594; Conflict</p></li><li><p><code>500</code> &#8594; Server Error</p></li></ul><p>Status codes are not optional metadata &#8212; they are part of the API contract.</p><div><hr></div><h2>4. Inconsistent or Vague Error Handling</h2><p>Two dangerous patterns:</p><ul><li><p>Generic error messages</p></li><li><p>Exposing stack traces to clients</p></li></ul><p><strong>&#9989; Better approach:</strong></p><p>Standardize error responses:</p><pre><code><code>{
  "code": "USER_NOT_FOUND",
  "message": "User with ID 123 does not exist."
}
</code></code></pre><p>Clear. Actionable. Secure.</p><div><hr></div><h2>5. Not Versioning Your API</h2><p>Launching without versioning feels fast.</p><p>It&#8217;s also short-sighted.</p><p><strong>&#10060; Mistake:</strong></p><pre><code><code>/users
</code></code></pre><p><strong>&#9989; Better:</strong></p><pre><code><code>/v1/users
</code></code></pre><p>Or use header-based versioning.</p><p>Versioning allows:</p><ul><li><p>Backward compatibility</p></li><li><p>Gradual deprecation</p></li><li><p>Safer evolution</p></li></ul><p>Without it, every breaking change becomes a production risk.</p><div><hr></div><h2>6. Poor Resource Structure &amp; Over-Nesting</h2><p>Deeply nested URLs become painful quickly.</p><p><strong>&#10060; Example:</strong></p><pre><code><code>/users/{id}/orders/{orderId}/items/{itemId}/reviews/{reviewId}
</code></code></pre><p>Hard to maintain. Hard to scale.</p><p><strong>&#9989; Instead:</strong><br>Use logical nesting but avoid excessive depth.<br>Consider top-level resources with relational links.</p><p>Example:</p><pre><code><code>/reviews/{id}
</code></code></pre><p>Keep URLs clean and predictable.</p><div><hr></div><h2>7. No Pagination, Filtering, or Sorting</h2><p>Returning entire datasets is a performance disaster waiting to happen.</p><p><strong>&#10060; Mistake:</strong></p><pre><code><code>GET /users
</code></code></pre><p>Returns 50,000 records.</p><p><strong>&#9989; Implement pagination:</strong></p><pre><code><code>GET /users?limit=20&amp;offset=40
</code></code></pre><p>Also allow:</p><ul><li><p>Filtering (<code>?status=active</code>)</p></li><li><p>Sorting (<code>?sort=created_at</code>)</p></li></ul><p>This dramatically improves performance and client flexibility.</p><div><hr></div><h2>8. Ignoring Security &amp; Throttling</h2><p>APIs without HTTPS, authentication, or rate limiting are exposed systems.</p><p><strong>Minimum standards today:</strong></p><ul><li><p>Always use <strong>HTTPS</strong></p></li><li><p>Implement authentication (OAuth2, JWT)</p></li><li><p>Add rate limiting</p></li></ul><p>Even a simple limit like <strong>100 requests per minute per user</strong> can prevent abuse.</p><p>Security is not an enhancement. It&#8217;s foundational.</p><div><hr></div><h2>9. Inconsistent Naming Conventions</h2><p>Mixing naming styles makes APIs confusing:</p><ul><li><p><code>/user</code></p></li><li><p><code>/users</code></p></li><li><p><code>/userList</code></p></li><li><p>camelCase vs snake_case</p></li></ul><p><strong>&#9989; Best Practice:</strong></p><ul><li><p>Use plural nouns: <code>/users</code></p></li><li><p>Choose one casing style and stick to it</p></li></ul><p>Consistency reduces cognitive load for developers.</p><div><hr></div><h2>10. Ignoring Caching &amp; Performance Tools</h2><p>APIs that don&#8217;t use caching headers are wasting performance potential.</p><p><strong>Use:</strong></p><ul><li><p><code>ETag</code></p></li><li><p><code>Cache-Control</code></p></li></ul><p>For long-running tasks:</p><pre><code><code>202 Accepted
</code></code></pre><p>Then provide a status endpoint.</p><p>Example:</p><pre><code><code>POST /reports
&#8594; 202 Accepted

GET /reports/{id}/status
</code></code></pre><p>Asynchronous processing makes your API scalable.</p><div><hr></div><h1>The Real Pattern Behind These Mistakes</h1><p>Notice something?</p><p>Every mistake above falls into one of three categories:</p><ol><li><p>Breaking HTTP semantics</p></li><li><p>Ignoring scalability</p></li><li><p>Failing to think long-term</p></li></ol><p>Good API design isn&#8217;t about being clever.</p><p>It&#8217;s about being predictable.</p><p>Because predictable APIs:</p><ul><li><p>Scale better</p></li><li><p>Break less</p></li><li><p>Are loved by developers</p></li></ul><div><hr></div><h1>REST API Design Checklist (Save This)</h1><p>Before shipping your next API:</p><p>&#10004; Resource-based URLs<br>&#10004; Correct HTTP methods<br>&#10004; Meaningful status codes<br>&#10004; Standardized error responses<br>&#10004; Versioning strategy<br>&#10004; Logical, shallow structure<br>&#10004; Pagination + filtering + sorting<br>&#10004; HTTPS + authentication + rate limiting<br>&#10004; Consistent naming conventions<br>&#10004; Caching + async support</p><p>If you get these 10 right, you&#8217;re ahead of most APIs in production.</p><p>Ship thoughtfully.</p><p>Your future self (and every developer who integrates with you) will thank you.</p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Dependency Injection in ASP.NET Core: Transient vs Scoped vs Singleton ]]></title><description><![CDATA[Understanding service lifetimes in ASP.NET Core with simple diagrams]]></description><link>https://newsletter.kanaiyakatarmal.com/p/dependency-injection-in-aspnet-core</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/dependency-injection-in-aspnet-core</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Mon, 12 Jan 2026 04:31:25 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mn_6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dependency Injection (DI) is one of the most powerful features in <strong>ASP.NET Core</strong>, but it&#8217;s also one of the most misunderstood&#8212;especially when it comes to <strong>service lifetimes</strong>.</p><p>If you&#8217;ve ever wondered:</p><ul><li><p><em>When should I use </em><code>AddTransient</code><em> vs </em><code>AddScoped</code><em>?</em></p></li><li><p><em>Why does using a Singleton sometimes cause bugs?</em></p></li><li><p><em>What actually happens per request?</em></p></li></ul><p>This article will clear that up using <strong>simple explanations and visual diagrams</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mn_6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mn_6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png 424w, https://substackcdn.com/image/fetch/$s_!mn_6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png 848w, https://substackcdn.com/image/fetch/$s_!mn_6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png 1272w, https://substackcdn.com/image/fetch/$s_!mn_6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mn_6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png" width="1020" height="574" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:574,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:199154,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/184224318?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mn_6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png 424w, https://substackcdn.com/image/fetch/$s_!mn_6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png 848w, https://substackcdn.com/image/fetch/$s_!mn_6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png 1272w, https://substackcdn.com/image/fetch/$s_!mn_6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c89253a-746e-4ed9-b932-c9316da4fabe_1020x574.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>What Is a Service Lifetime?</h2><p>In ASP.NET Core, a <strong>service lifetime</strong> defines <strong>how long an object lives</strong> in memory and <strong>how often a new instance is created</strong>.</p><p>.NET Core provides <strong>three built-in lifetimes</strong>:</p><ol><li><p><strong>Transient</strong></p></li><li><p><strong>Scoped</strong></p></li><li><p><strong>Singleton</strong></p></li></ol><p>Let&#8217;s break them down one by one.</p><h2>1. Transient Lifetime</h2><p><strong>Definition:</strong><br>A new instance is created <strong>every time</strong> the service is requested.</p><h3>Registration</h3><pre><code>services.AddTransient&lt;IService, Service&gt;();</code></pre><h3>How It Works (from the diagram)</h3><ul><li><p>Every request gets <strong>a brand-new object</strong></p></li><li><p>Even within the same request, multiple injections create <strong>multiple instances</strong></p></li></ul><h3>When to Use Transient</h3><p>&#10004; Lightweight services<br>&#10004; Stateless services<br>&#10004; Helper or utility classes</p><p><strong>Example</strong></p><pre><code>public class EmailFormatter
{
    public string Format(string message) =&gt; $"Email: {message}";
}</code></pre><h3>&#9888;&#65039; Avoid When</h3><ul><li><p>The service holds state</p></li><li><p>The service is expensive to create</p></li></ul><p><strong>Think of Transient as:</strong></p><blockquote><p>&#8220;Create it, use it, throw it away.&#8221;</p></blockquote><h2>2. Scoped Lifetime</h2><p><strong>Definition:</strong><br>One instance is created <strong>per request (scope)</strong>.</p><h3>Registration</h3><pre><code>services.AddScoped&lt;IService, Service&gt;();</code></pre><h3>How It Works (from the diagram)</h3><ul><li><p>Each HTTP request gets <strong>one instance</strong></p></li><li><p>The same instance is reused <strong>within that request</strong></p></li><li><p>A new request = a new instance</p></li></ul><h3>When to Use Scoped</h3><p>&#10004; Database contexts (<code>DbContext</code>)<br>&#10004; Business logic tied to a request<br>&#10004; Services that track request-specific data</p><p>Example</p><pre><code>public class OrderService
{
    private readonly Guid _requestId = Guid.NewGuid();
}</code></pre><p>Every HTTP request gets its <strong>own </strong><code>OrderService</code>, but controllers inside the same request share it.</p><h3>&#9888;&#65039; Important Rule</h3><blockquote><p><strong>Never inject a Scoped service into a Singleton</strong></p></blockquote><p>This will cause runtime errors or subtle bugs.</p><p><strong>Think of Scoped as:</strong></p><blockquote><p>&#8220;One service per web request.&#8221;</p></blockquote><h2>3. Singleton Lifetime</h2><p><strong>Definition:</strong><br>A <strong>single instance</strong> is created and shared for the <strong>entire application lifetime</strong>.</p><h3>Registration</h3><pre><code><code>services.AddSingleton&lt;IService, Service&gt;();
</code></code></pre><h3>How It Works (from the diagram)</h3><ul><li><p>Created once when the app starts</p></li><li><p>Shared across <strong>all requests and users</strong></p></li><li><p>Lives until the application shuts down</p></li></ul><h3>When to Use Singleton</h3><p>&#10004; Configuration services<br>&#10004; Caching services<br>&#10004; Logging services<br>&#10004; Thread-safe, read-only services</p><p><strong>Example</strong></p><pre><code>public class AppSettingsCache
{
    public Dictionary&lt;string, string&gt; Settings { get; } = new();
}</code></pre><h3>&#9888;&#65039; Be Careful</h3><ul><li><p>Must be <strong>thread-safe</strong></p></li><li><p>Shared state can cause <strong>race conditions</strong></p></li><li><p>Never inject <code>Scoped</code> or <code>Transient</code> services that depend on request data</p></li></ul><p><strong>Think of Singleton as:</strong></p><blockquote><p>&#8220;One object to rule them all.&#8221;</p></blockquote><p></p><h2>Common Mistakes (and How to Avoid Them)</h2><p>&#10060; Using Singleton for database access<br>&#9989; Use Scoped instead</p><p>&#10060; Storing user-specific data in Singleton<br>&#9989; Keep user data request-scoped</p><p>&#10060; Overusing Transient for heavy services<br>&#9989; Use Scoped or Singleton where appropriate</p><h2>Final Thoughts</h2><p>Choosing the right service lifetime is <strong>not just about performance</strong>&#8212;it&#8217;s about <strong>correctness, scalability, and safety</strong>.</p><p>If you remember just one thing:</p><ul><li><p><strong>Transient</strong> &#8594; lightweight &amp; stateless</p></li><li><p><strong>Scoped</strong> &#8594; request-based logic</p></li><li><p><strong>Singleton</strong> &#8594; shared &amp; thread-safe</p></li></ul><p>Pick wisely, and your ASP.NET Core apps will thank you.</p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[EF Core Global Query Filters: A Complete Guide ]]></title><description><![CDATA[How to Write Cleaner, Safer, and More Maintainable EF Core Queries]]></description><link>https://newsletter.kanaiyakatarmal.com/p/ef-core-global-query-filters-a-complete</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/ef-core-global-query-filters-a-complete</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Mon, 15 Dec 2025 04:31:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Sm6u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When working with Entity Framework Core, it&#8217;s common to repeatedly filter out soft-deleted or archived records. Maybe you only want to show <em>active</em> customers, valid products, or non-archived documents.</p><p>Instead of remembering to add these conditions everywhere, EF Core gives us a powerful tool to automate this logic: <strong>Global Query Filters</strong>.</p><p>In this article, we&#8217;ll explore what they are, why they matter, and how to use them effectively&#8212;including a practical soft-delete example you can copy into your own project.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Sm6u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Sm6u!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png 424w, https://substackcdn.com/image/fetch/$s_!Sm6u!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png 848w, https://substackcdn.com/image/fetch/$s_!Sm6u!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png 1272w, https://substackcdn.com/image/fetch/$s_!Sm6u!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Sm6u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png" width="1078" height="606" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/64972e10-9476-4a57-acf9-239faf573413_1078x606.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:606,&quot;width&quot;:1078,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56285,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/178015470?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Sm6u!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png 424w, https://substackcdn.com/image/fetch/$s_!Sm6u!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png 848w, https://substackcdn.com/image/fetch/$s_!Sm6u!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png 1272w, https://substackcdn.com/image/fetch/$s_!Sm6u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64972e10-9476-4a57-acf9-239faf573413_1078x606.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>What Are Global Query Filters?</h2><p>A <strong>global query filter</strong> is a LINQ expression automatically applied to an entity every time EF Core generates a query.</p><p>Once defined:</p><ul><li><p>You <strong>don&#8217;t have to add the filter manually</strong> to each query</p></li><li><p>EF Core <strong>injects the filter into all queries</strong>, including navigation loads and LINQ queries</p></li><li><p>You can still <strong>bypass</strong> the filter when needed</p></li></ul><p>This leads to cleaner, safer, and more maintainable code especially when dealing with soft deletes, permissions, or multi-tenant data.</p><h2>The Soft Delete Scenario</h2><p>Soft deletion is extremely common. Instead of physically deleting a record from the database, you mark it as deleted:</p><pre><code>public class Customer
{
    public int Id { get; set; }
    public bool IsDeleted { get; set; }
}</code></pre><p>Without global filters, every query would need:</p><pre><code>.Where(c =&gt; !c.IsDeleted)</code></pre><p>And it&#8217;s only a matter of time before someone forgets it.</p><h2>Applying a Global Query Filter</h2><p>Global filters are configured inside <code>OnModelCreating</code>.</p><p>Here&#8217;s your example in full:</p><pre><code>public class CustomerContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Global query filter for soft delete
        modelBuilder.Entity&lt;Customer&gt;()
            .HasQueryFilter(p =&gt; !p.IsDeleted);
    }
}</code></pre><p>From this moment on, EF Core automatically adds:</p><pre><code>WHERE IsDeleted = 0</code></pre><p>to every query involving <code>Customer</code>.</p><p>&#9989; Using Queries with the Filter Applied</p><pre><code>// Filtered query: soft-deleted customers are excluded
var activeCustomers = await context.Customers
    .ToListAsync(cancellationToken);</code></pre><p>Even though you didn&#8217;t specify a condition, EF Core did it for you behind the scenes.<br>This ensures your application never accidentally exposes deleted or archived records.</p><h2>Bypassing the Filter When Needed</h2><p>Sometimes you need access to <em>all</em> customers&#8212;for example, an admin tool, an audit job, or a restore action.</p><p>EF Core makes that easy:</p><pre><code>// Unfiltered query: includes soft-deleted customers
var customers = await context.Customers
    .IgnoreQueryFilters()
    .ToListAsync(cancellationToken);</code></pre><p>This gives you full control while still keeping your main queries safe.</p><h2>More Advanced: Named Global Query Filters (EF Core 10)</h2><p>EF Core 10 introduces <strong>named global filters</strong>, letting you toggle specific filters on and off (similar to feature flags for filtering).</p><p>This is especially useful when you have <strong>multiple filters on the same entity</strong>, such as:</p><ul><li><p><code>IsDeleted == false</code></p></li><li><p><code>IsArchived == false</code></p></li><li><p><code>TenantId == CurrentTenant</code></p></li></ul><p>You can selectively disable only one filter instead of removing all of them via <code>IgnoreQueryFilters()</code>.</p><p>This feature comes from a great community discussion and design proposal&#8212;if you&#8217;re curious, Tim Deschryver provides a deep dive here: <em>(link you provided)</em>.</p><h2>Why Global Query Filters Are So Powerful</h2><h3><em>The benefits that scale with your application</em></h3><ul><li><p><strong>Centralized filtering logic</strong></p></li><li><p><strong>Prevents accidental data exposure</strong></p></li><li><p><strong>Great for soft delete, multi-tenancy, and security rules</strong></p></li><li><p><strong>Applies to LINQ queries and navigation properties</strong></p></li></ul><p>Once you start using them, it&#8217;s hard to imagine building an EF Core application without them.</p><h2>Common Pitfalls to Avoid</h2><ul><li><p><strong>Filters are applied to navigation properties</strong>, which may cause unexpected query results if you&#8217;re not aware.</p></li><li><p><strong>Owned entity types can&#8217;t have filters</strong>.</p></li><li><p><strong>Filters are cached per model</strong>, so dynamic per-request values require storing state in the DbContext (e.g., current tenant).</p></li></ul><h2>Looking Ahead: Named Filters in EF Core 10</h2><h3><em>Fine-grained control for advanced scenarios</em></h3><p>EF Core 10 introduces <strong>named global query filters</strong>, allowing developers to disable specific filters instead of all filters at once.</p><p>This is especially useful when combining:</p><ul><li><p>Soft delete</p></li><li><p>Archiving</p></li><li><p>Multi-tenancy</p></li></ul><p>If you&#8217;re curious about the detailed proposal and examples, check out the original discussion referenced in your link.</p><h2>Final Thoughts</h2><p>Global Query Filters are one of EF Core&#8217;s most elegant ways to simplify your code and enforce data rules consistently. For scenarios like soft delete, they provide:</p><ul><li><p>Cleaner code</p></li><li><p>Safer access patterns</p></li><li><p>More maintainable systems</p></li></ul><p>Start with a simple soft-delete filter like the one above, and soon you&#8217;ll find ways to use them across your entire application.</p><p></p><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/EFCoreGlobalQueryFilters">https://sourcecode.kanaiyakatarmal.com/EFCoreGlobalQueryFilters</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[EF Core 10 Makes Outer Joins Beautiful Again]]></title><description><![CDATA[Left & Right Joins now feel as natural as SQL.]]></description><link>https://newsletter.kanaiyakatarmal.com/p/ef-core-10-makes-outer-joins-beautiful</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/ef-core-10-makes-outer-joins-beautiful</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Mon, 08 Dec 2025 04:31:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9pmj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Entity Framework Core has come a long way, but one thing that always felt awkward was performing <strong>outer joins</strong>. While SQL offers clean LEFT and RIGHT join syntax, LINQ forced developers into more verbose patterns especially when retrieving all records from one table regardless of match.</p><p>With <strong>EF Core 10</strong>, that finally changes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9pmj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9pmj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png 424w, https://substackcdn.com/image/fetch/$s_!9pmj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png 848w, https://substackcdn.com/image/fetch/$s_!9pmj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png 1272w, https://substackcdn.com/image/fetch/$s_!9pmj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9pmj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png" width="1090" height="612" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:612,&quot;width&quot;:1090,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63177,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/180973733?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9pmj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png 424w, https://substackcdn.com/image/fetch/$s_!9pmj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png 848w, https://substackcdn.com/image/fetch/$s_!9pmj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png 1272w, https://substackcdn.com/image/fetch/$s_!9pmj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7456f25a-4c8b-4f9e-92ed-fdd5f8b93881_1090x612.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Why Outer Joins Matter</h2><p>In real projects, we often need to fetch <em>all</em> entities from one table, including those with no related data. For example:</p><ul><li><p>A campaign may exist even if no assets were uploaded yet</p></li><li><p>A file could be imported before a campaign is attached to it</p></li><li><p>Reports often need <em>complete</em> datasets, even with missing associations</p></li></ul><p>Until now, LINQ made this harder than necessary.</p><p>EF Core 10 changes that elegantly.</p><div><hr></div><h2>Example Scenario</h2><p>Let&#8217;s imagine a common marketing structure:</p><ul><li><p><strong>Campaign</strong> &#8594; a promotional or marketing effort</p></li><li><p><strong>CampaignAsset</strong> &#8594; images, files, or collateral linked to the campaign</p></li></ul><p></p><p>Entity Definitions</p><pre><code>public class Campaign
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

public class CampaignAsset
{
    public int Id { get; set; }
    public int CampaignId { get; set; }
    public string FileName { get; set; } = string.Empty;
}</code></pre><p>Our goal: <strong>retrieve all campaigns &#8212; even those without assets.</strong></p><h2>EF Core 10: Left Join (Left Outer Join)</h2><p>EF Core 10 introduces a clean new operator: <code>.LeftJoin()</code>, making this operation finally feel natural.</p><pre><code>var query = context.Campaigns
    .LeftJoin(
        context.CampaignAssets,
        campaign =&gt; campaign.Id,
        asset =&gt; asset.CampaignId,
        (campaign, asset) =&gt; new
        {
            Campaign = campaign,
            Asset = asset
        });</code></pre><p><strong>This is LINQ the way it was meant to be.</strong></p><h3>SQL Generated</h3><pre><code>SELECT *
FROM Campaigns AS c
LEFT JOIN CampaignAssets AS a
    ON c.Id = a.CampaignId;</code></pre><h2>EF Core 10: Right Join (Right Outer Join)</h2><p>Less common, but extremely useful when reconciling external data sources or validating imported files.</p><pre><code>var query = context.Campaigns
    .RightJoin(
        context.CampaignAssets,
        campaign =&gt; campaign.Id,
        asset =&gt; asset.CampaignId,
        (campaign, asset) =&gt; new
        {
            Campaign = campaign,
            Asset = asset
        });</code></pre><p>Right joins now require no workaround &#8212; finally.</p><h2>Before vs After EF Core 10</h2><p>&#10060; Old Approach &#8212; Functional, but clunky</p><pre><code>var query =
    from c in context.Campaigns
    join a in context.CampaignAssets
        on c.Id equals a.CampaignId into assets
    from a in assets.DefaultIfEmpty()
    select new { c, a };</code></pre><p>It works &#8212; but the <em>intent</em> is buried in syntax.</p><p>&#9989; New EF Core 10 Approach &#8212; Elegant &amp; expressive</p><pre><code>var query = context.Campaigns
    .LeftJoin(
        context.CampaignAssets,
        c =&gt; c.Id,
        a =&gt; a.CampaignId,
        (c, a) =&gt; new { c, a });</code></pre><p>Readable. Direct. Maintainable.<br>This is what modern LINQ should look like.</p><h2>Final Thoughts</h2><p>EF Core 10 didn&#8217;t just add an operator &#8212; it improved the developer experience.<br>Outer joins are finally straightforward in LINQ, making queries easier to read, write, and teach.</p><p><strong>If you&#8217;re using EF Core professionally, this is an upgrade worth adopting.</strong><br>Clean code scales &#8212; and this feature is a perfect example.</p><p></p><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/EFCore10OuterJoins">https://sourcecode.kanaiyakatarmal.com/EFCore10OuterJoins</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Understanding Modern API Communication Technologies]]></title><description><![CDATA[A Deep Dive into How Today&#8217;s Systems Connect, Integrate, and Exchange Data]]></description><link>https://newsletter.kanaiyakatarmal.com/p/understanding-modern-api-communication</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/understanding-modern-api-communication</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Tue, 25 Nov 2025 06:36:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CjZ1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today&#8217;s digital ecosystem, applications rarely operate in isolation. They communicate, exchange data, stream media, and deliver real-time updates. To enable this interaction efficiently, several communication technologies and protocols are used each designed with specific strengths and use cases.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CjZ1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CjZ1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png 424w, https://substackcdn.com/image/fetch/$s_!CjZ1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png 848w, https://substackcdn.com/image/fetch/$s_!CjZ1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png 1272w, https://substackcdn.com/image/fetch/$s_!CjZ1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CjZ1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png" width="655" height="371" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:371,&quot;width&quot;:655,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:170767,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/178114978?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CjZ1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png 424w, https://substackcdn.com/image/fetch/$s_!CjZ1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png 848w, https://substackcdn.com/image/fetch/$s_!CjZ1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png 1272w, https://substackcdn.com/image/fetch/$s_!CjZ1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a3d33ac-a886-40c8-82f9-1ec2838252c6_655x371.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The image above highlights <strong>seven</strong> widely-used approaches:</p><div><hr></div><h2><strong>1. REST API</strong></h2><p>REST (Representational State Transfer) is the most commonly used architectural style for building web services.</p><p><strong>Key characteristics:</strong></p><ul><li><p>Works over HTTP</p></li><li><p>Stateless</p></li><li><p>Supports JSON, XML, and other data formats</p></li><li><p>Easy to use and widely adopted</p></li></ul><p><strong>Best for:</strong></p><ul><li><p>CRUD operations</p></li><li><p>Standard web/mobile APIs</p></li><li><p>Public APIs (e.g., Twitter, GitHub)</p></li></ul><div><hr></div><h2><strong>2. GraphQL</strong></h2><p>GraphQL is a query language for APIs created by Facebook.</p><p><strong>Why it&#8217;s popular:</strong></p><ul><li><p>Client can request exactly the data needed</p></li><li><p>Reduces over-fetching and under-fetching</p></li><li><p>Single endpoint for all queries and mutations</p></li></ul><p><strong>Best for:</strong></p><ul><li><p>Complex data relationships</p></li><li><p>Mobile apps (bandwidth-sensitive)</p></li><li><p>Frontend-heavy applications</p></li></ul><div><hr></div><h2><strong>3. SOAP API</strong></h2><p>SOAP (Simple Object Access Protocol) is a strict protocol used mostly in enterprise systems.</p><p><strong>Characteristics:</strong></p><ul><li><p>Enforces strict standards</p></li><li><p>Built-in security and error handling</p></li><li><p>Uses XML</p></li></ul><p><strong>Best for:</strong></p><ul><li><p>Banking</p></li><li><p>Telecom</p></li><li><p>Payment gateways</p></li><li><p>Government systems</p></li></ul><div><hr></div><h2><strong>4. gRPC</strong></h2><p>Google&#8217;s Remote Procedure Call framework, powered by HTTP/2.</p><p><strong>Benefits:</strong></p><ul><li><p>High performance</p></li><li><p>Bi-directional streaming</p></li><li><p>Strongly typed contracts using Protocol Buffers</p></li><li><p>Lightweight and fast</p></li></ul><p><strong>Best for:</strong></p><ul><li><p>Microservices communication</p></li><li><p>Real-time communication</p></li><li><p>Low-latency systems</p></li></ul><div><hr></div><h2><strong>5. Webhooks</strong></h2><p>Webhooks are event-driven HTTP callbacks.</p><p><strong>How they work:</strong></p><ul><li><p>Server notifies another system when an event occurs</p></li><li><p>No constant polling required</p></li></ul><p><strong>Use cases:</strong></p><ul><li><p>Payment status update</p></li><li><p>Slack notifications</p></li><li><p>CI/CD triggers</p></li></ul><p><strong>Best for:</strong></p><ul><li><p>Instant push notifications between systems</p></li></ul><div><hr></div><h2><strong>6. WebSockets</strong></h2><p>WebSockets enable persistent, full-duplex communication between client and server.</p><p><strong>Advantages:</strong></p><ul><li><p>Real-time updates</p></li><li><p>Single TCP connection</p></li><li><p>Low latency</p></li></ul><p><strong>Best for:</strong></p><ul><li><p>Live dashboards</p></li><li><p>Chat applications</p></li><li><p>Multiplayer games</p></li></ul><div><hr></div><h2><strong>7. WebRTC</strong></h2><p>WebRTC (Web Real-Time Communication) enables browser-to-browser media communication without plugins.</p><p><strong>Capabilities:</strong></p><ul><li><p>Audio/video streaming</p></li><li><p>Peer-to-peer connections</p></li><li><p>Data channels</p></li></ul><p><strong>Use cases:</strong></p><ul><li><p>Video conferencing (Zoom, Meet)</p></li><li><p>Live streaming</p></li><li><p>Virtual events</p></li></ul><div><hr></div><h2><strong>How to Choose the Right Technology </strong></h2><h3>When you need standard CRUD operations</h3><p>Use <strong>REST API</strong></p><ul><li><p>Easy to implement</p></li><li><p>Well supported everywhere</p></li><li><p>Perfect for typical web/mobile apps</p></li></ul><div><hr></div><h3>When you want efficient data fetching</h3><p>Use <strong>GraphQL</strong></p><ul><li><p>Clients can request only required fields</p></li><li><p>Prevents over-fetching/under-fetching</p></li><li><p>Best for complex UI data requirements</p></li></ul><div><hr></div><h3>When your system requires high security &amp; strict contracts</h3><p>Use <strong>SOAP</strong></p><ul><li><p>Built-in security features</p></li><li><p>Strong validation</p></li><li><p>Ideal for enterprise &amp; banking</p></li></ul><div><hr></div><h3>When you need high-performance internal communication</h3><p>Use <strong>gRPC</strong></p><ul><li><p>Faster than REST</p></li><li><p>Low-latency communication</p></li><li><p>Ideal for microservices</p></li></ul><div><hr></div><h3>When you want event-based callbacks</h3><p>Use <strong>Webhooks</strong></p><ul><li><p>Push-based notifications</p></li><li><p>No need for polling</p></li><li><p>Great for payment/platform integrations</p></li></ul><div><hr></div><h3>When you need real-time, continuous messaging</h3><p>Use <strong>WebSockets</strong></p><ul><li><p>Persistent connection</p></li><li><p>Bi-directional communication</p></li><li><p>Ideal for chats, dashboards, games</p></li></ul><div><hr></div><h3>When you want direct peer-to-peer audio/video/data transfer</h3><p>Use <strong>WebRTC</strong></p><ul><li><p>Browser-to-browser communication</p></li><li><p>Low latency</p></li><li><p>Ideal for video calling &amp; streaming</p></li></ul><div><hr></div><h2>Quick Decision Flow (Simple)</h2><ul><li><p><strong>CRUD APIs?</strong> &#8594; REST</p></li><li><p><strong>Complex queries?</strong> &#8594; GraphQL</p></li><li><p><strong>Enterprise compliance/security?</strong> &#8594; SOAP</p></li><li><p><strong>Service-to-service speed?</strong> &#8594; gRPC</p></li><li><p><strong>Trigger events externally?</strong> &#8594; Webhooks</p></li><li><p><strong>Real-time messages?</strong> &#8594; WebSockets</p></li><li><p><strong>Video/audio communication?</strong> &#8594; WebRTC</p></li></ul><h1>Conclusion</h1><p>Each of these communication technologies solves a different problem. Understanding their strengths helps engineers architect reliable, scalable, and efficient systems.</p><p>Instead of comparing them as competitors, think of them as <strong>tools in a toolbox</strong>&#8212;choose the one that fits the job.</p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Mastering the Strategy Pattern in C#: A Complete Guide]]></title><description><![CDATA[Design Flexible, Extensible, and Maintainable Applications Using Strategy Pattern Techniques]]></description><link>https://newsletter.kanaiyakatarmal.com/p/mastering-the-strategy-pattern-in</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/mastering-the-strategy-pattern-in</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Tue, 18 Nov 2025 04:40:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kVsK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Design patterns are essential tools for building maintainable, flexible, and scalable software. One of the most powerful behavioral patterns in object-oriented programming is the <strong>Strategy Pattern</strong>. In this article, we&#8217;ll explore the Strategy Pattern in C#, understand when and how to use it, and provide practical examples that you can implement in your projects.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kVsK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kVsK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png 424w, https://substackcdn.com/image/fetch/$s_!kVsK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png 848w, https://substackcdn.com/image/fetch/$s_!kVsK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png 1272w, https://substackcdn.com/image/fetch/$s_!kVsK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kVsK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png" width="1091" height="612" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:612,&quot;width&quot;:1091,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:54357,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/176589840?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kVsK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png 424w, https://substackcdn.com/image/fetch/$s_!kVsK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png 848w, https://substackcdn.com/image/fetch/$s_!kVsK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png 1272w, https://substackcdn.com/image/fetch/$s_!kVsK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a83f3ee-2ea5-4327-b708-127f38ed22f7_1091x612.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>What is the Strategy Pattern?</h2><p>The <strong>Strategy Pattern</strong> is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. Instead of hardcoding multiple conditional statements or behaviors, you can dynamically change the behavior of a class at runtime.</p><p>In simpler terms:</p><blockquote><p>&#8220;Encapsulate algorithms in separate classes and let the client choose which one to use.&#8221;</p></blockquote><p></p><h2>When to Use the Strategy Pattern</h2><p>The Strategy Pattern is particularly useful when:</p><ul><li><p>You have multiple ways to perform a specific task.</p></li><li><p>You want to avoid complex <code>if-else</code> or <code>switch</code> statements.</p></li><li><p>You want to make your code <strong>open for extension but closed for modification</strong> (following the Open/Closed Principle).</p></li></ul><p><strong>Examples in real-world scenarios:</strong></p><ul><li><p>Payment processing (Credit Card, PayPal, Cryptocurrency)</p></li><li><p>Sorting algorithms (QuickSort, MergeSort, BubbleSort)</p></li><li><p>Logging strategies (File, Database, Console)</p></li></ul><p></p><h2>Anatomy of the Strategy Pattern</h2><p>The Strategy Pattern consists of three main components:</p><ol><li><p><strong>Strategy Interface</strong>: Declares a common interface for all supported algorithms.</p></li><li><p><strong>Concrete Strategies</strong>: Implement the Strategy interface with specific algorithms.</p></li><li><p><strong>Context</strong>: Maintains a reference to a Strategy object and delegates behavior execution to it.</p></li></ol><p></p><h2>mplementing Strategy Pattern in C#</h2><p>Let&#8217;s walk through a real-world example: a <strong>Payment Processor</strong>.</p><h3>Step 1: Define the Strategy Interface</h3><pre><code>public interface IPaymentStrategy
{
    void Pay(decimal amount);
}</code></pre><h3>Step 2: Create Concrete Strategies</h3><pre><code>public class CreditCardPayment : IPaymentStrategy
{
    private string _cardNumber;

    public CreditCardPayment(string cardNumber)
    {
        _cardNumber = cardNumber;
    }

    public void Pay(decimal amount)
    {
        Console.WriteLine($&#8221;Paid {amount:C} using Credit Card {_cardNumber}&#8221;);
    }
}

public class PayPalPayment : IPaymentStrategy
{
    private string _email;

    public PayPalPayment(string email)
    {
        _email = email;
    }

    public void Pay(decimal amount)
    {
        Console.WriteLine($&#8221;Paid {amount:C} using PayPal account {_email}&#8221;);
    }
}</code></pre><h3>Step 3: Implement the Context</h3><pre><code>public class PaymentProcessor
{
    private IPaymentStrategy _paymentStrategy;

    public PaymentProcessor(IPaymentStrategy paymentStrategy)
    {
        _paymentStrategy = paymentStrategy;
    }

    public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
    {
        _paymentStrategy = paymentStrategy;
    }

    public void ProcessPayment(decimal amount)
    {
        _paymentStrategy.Pay(amount);
    }
}</code></pre><h3>Step 4: Use the Strategy Pattern</h3><pre><code>class Program
{
    static void Main(string[] args)
    {
        var creditCardPayment = new CreditCardPayment("1234-5678-9876-5432");
        var paypalPayment = new PayPalPayment("user@example.com");

        var processor = new PaymentProcessor(creditCardPayment);
        processor.ProcessPayment(100); // Paid 100 using Credit Card 1234-5678-9876-5432

        processor.SetPaymentStrategy(paypalPayment);
        processor.ProcessPayment(200); // Paid 200 using PayPal account user@example.com
    }
}</code></pre><h2>Advantages of the Strategy Pattern</h2><ul><li><p><strong>Flexibility</strong>: Easily swap algorithms without modifying the client.</p></li><li><p><strong>Single Responsibility</strong>: Each algorithm is encapsulated in its own class.</p></li><li><p><strong>Open/Closed Principle</strong>: Adding a new algorithm does not require changing existing code.</p></li></ul><h2>Disadvantages</h2><ul><li><p><strong>Increased number of classes</strong>: Each algorithm requires a separate class.</p></li><li><p><strong>Complexity</strong>: Can add complexity if overused for simple tasks.</p></li></ul><p></p><h2>Real-World Example</h2><p>Imagine an e-commerce platform where you need to implement different shipping methods: <strong>Standard Shipping</strong>, <strong>Express Shipping</strong>, and <strong>Overnight Shipping</strong>. Using the Strategy Pattern, you can create a flexible system where new shipping strategies can be added without modifying the core checkout logic.</p><p></p><h2>Conclusion</h2><p>The Strategy Pattern is a powerful design pattern for situations where multiple algorithms exist, and you want to dynamically select the best one at runtime. By encapsulating algorithms into separate classes and using a common interface, your code becomes cleaner, more maintainable, and easily extendable.</p><p>Using the Strategy Pattern in C# can drastically reduce your conditional logic, improve code readability, and enhance scalability in large applications.</p><p>A <strong>class</strong> in C# is a <strong>reference type</strong>, meaning it is stored on the <strong>heap</strong>, and assignments create references to the same object. Classes support inheritance, encapsulation, and polymorphism.</p><p></p><h3>&#128313; Ready to refactor your code with Strategy Pattern?</h3><p>Which part of your current project could benefit from dynamically swapping behaviors?</p><p></p><p></p><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/StrategyPattern">https://sourcecode.kanaiyakatarmal.com/StrategyPattern</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Boost Your API Performance with gRPC in .NET Core]]></title><description><![CDATA[Discover how gRPC transforms API speed and efficiency in modern .NET Core applications. A practical guide to building ultra-fast, scalable, and reliable services.]]></description><link>https://newsletter.kanaiyakatarmal.com/p/boost-your-api-performance-with-grpc</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/boost-your-api-performance-with-grpc</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Fri, 14 Nov 2025 04:31:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gbRH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As distributed systems grow more complex, traditional REST APIs often fall short in scenarios demanding <strong>low latency</strong>, <strong>real-time streaming</strong>, or <strong>high throughput</strong> communication.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gbRH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gbRH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png 424w, https://substackcdn.com/image/fetch/$s_!gbRH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png 848w, https://substackcdn.com/image/fetch/$s_!gbRH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png 1272w, https://substackcdn.com/image/fetch/$s_!gbRH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gbRH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png" width="754" height="397" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:397,&quot;width&quot;:754,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:114700,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/178015433?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gbRH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png 424w, https://substackcdn.com/image/fetch/$s_!gbRH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png 848w, https://substackcdn.com/image/fetch/$s_!gbRH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png 1272w, https://substackcdn.com/image/fetch/$s_!gbRH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8cacbded-8692-487d-93cf-8dcec78b8ada_754x397.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>What is gRPC?</h2><p>gRPC (Google Remote Procedure Call) is a modern, open-source framework built on <strong>HTTP/2</strong> and uses <strong>Protocol Buffers (protobuf)</strong> for serialization. Instead of sending verbose JSON payloads, protobuf creates <strong>compact, binary-encoded messages</strong> that are faster and smaller.</p><p><strong>In simple words:</strong></p><p>&#128073; REST = &#8220;Send me data&#8221;<br>&#128073; gRPC = &#8220;Call my function remotely, as if it existed locally&#8221;</p><div><hr></div><h2>&#129504; Why gRPC in .NET Core?</h2><p>Microsoft has deeply integrated gRPC support in .NET Core, offering developers:</p><p>&#9989; Strong type safety<br>&#9989; Contract-driven development<br>&#9989; First-class tooling support<br>&#9989; Auto-generated clients</p><p>This reduces boilerplate and ensures consistent communication between services.</p><div><hr></div><h2>&#9889; gRPC vs REST (High-Level Differences)</h2><p><strong>gRPC shines when you need:</strong></p><ul><li><p>Microservice-to-microservice calls</p></li><li><p>Real-time streaming</p></li><li><p>High request frequency</p></li><li><p>Maximum performance</p></li></ul><p>REST is still great for:</p><ul><li><p>Public/external APIs</p></li><li><p>Simple CRUD operations</p></li><li><p>Browsers without gRPC support</p></li></ul><p>They are not competitors &#8212; they serve different purposes.</p><h2>Project Structure Overview</h2><p>Our microservice uses some common building blocks:</p><ul><li><p><strong>Product Model</strong> &#8212; represents the domain entity</p></li><li><p><strong>AppDbContext</strong> &#8212; EF Core database context</p></li><li><p><strong>Proto Contract</strong> &#8212; gRPC messages &amp; RPC definitions</p></li><li><p><strong>ProductService</strong> &#8212; implementation of RPC endpoints</p></li></ul><pre><code>/GRPCSample
 &#9500;&#9472;&#9472; Data
 &#9500;&#9472;&#9472; Models
 &#9500;&#9472;&#9472; Services
 &#9500;&#9472;&#9472; Protos
 &#9492;&#9472;&#9472; Program.cs</code></pre><h2>The Entity (Product)</h2><p>Inside <code>Models/Product.cs</code>, we define the Product entity used by EF Core:</p><pre><code>public class Product
{
    public Guid Id { get; set; }
    public required string Name { get; set; }
    public required string Description { get; set; }
    public decimal Price { get; set; }
    public DateTime Created { get; set; }
    public DateTime Updated { get; set; }
    public string? Tags { get; set; }
}</code></pre><p>Couple of noteworthy things here:</p><ul><li><p><code>required</code> ensures properties must be set.</p></li><li><p><code>Created</code> and <code>Updated</code> manage timestamps.</p></li><li><p><code>Tags</code> is optional.</p></li></ul><h2>Database Context Setup</h2><p>Using Entity Framework Core, we define a context exposing the products table:</p><pre><code>public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions&lt;AppDbContext&gt; options)
        : base(options) {}

    public DbSet&lt;Product&gt; Products =&gt; Set&lt;Product&gt;();
}</code></pre><p>This allows EF Core to map the <code>Product</code> entity automatically.</p><h2>Defining gRPC Contracts With Protobuf</h2><p>Inside <code>product.proto</code>, we define messages and service operations:</p><pre><code>syntax = "proto3";

option csharp_namespace = "GRPCSample";

package product;

service ProductsServiceProto {
  rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse);
  rpc GetProduct(GetProductRequest) returns (GetProductResponse);
  rpc ListProduct(ListProductsRequest) returns (ListProductsResponse);
  rpc UpdateProduct(UpdateProductRequest) returns (UpdateProductResponse);
  rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse);
}

message ProductModel {
  string id = 1;
  string name = 2;
  string description = 3;
  double price = 4;
  string created_at = 5;
  string updated_at = 6;
  string tag = 7;
}</code></pre><p>All communication between client and server is strongly typed and version-safe.</p><p>Registering the Proto in the Project File</p><pre><code>&lt;ItemGroup&gt;
   &lt;Protobuf Include="Protos\product.proto" GrpcServices="Server" /&gt;
&lt;/ItemGroup&gt;</code></pre><p>And reference the gRPC package:</p><pre><code>&lt;PackageReference Include="Grpc.AspNetCore" Version="2.71.0" /&gt;</code></pre><p>Implementing the Service Layer</p><pre><code>public class ProductService : ProductsServiceProto.ProductsServiceProtoBase</code></pre><p><strong>Create Product</strong></p><pre><code>public override async Task&lt;CreateProductResponse&gt; CreateProduct(CreateProductRequest request, ServerCallContext context)
{
    var productItem = new Product { ... };

    _dbContext.Products.Add(productItem);
    await _dbContext.SaveChangesAsync();
}</code></pre><p><strong>Get Product by ID</strong></p><pre><code>var product = await _dbContext.Products.FindAsync(productId);</code></pre><p><strong>Update Product</strong></p><pre><code>existingProduct.Updated = DateTime.UtcNow;
await _dbContext.SaveChangesAsync();</code></pre><p><strong>Delete Product</strong></p><pre><code>dbContext.Products.Remove(product);
await _dbContext.SaveChangesAsync();</code></pre><p><strong>Mapping Entity &#8594; Proto Model</strong></p><pre><code>private static ProductModel MapToProductModel(Product product)
{
    return new ProductModel
    {
        Id = product.Id.ToString(),
        Name = product.Name,
        ...
    };
}</code></pre><h3><strong>Creating and Running Migrations</strong></h3><p>Start by creating the initial migration:</p><pre><code>dotnet ef migrations add InitialCreate</code></pre><p>Apply the migration to create the database:</p><pre><code>dotnet ef database update</code></pre><p></p><h2><strong>Test gRPC Services with Postman</strong></h2><p>Create a New gRPC Request</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!br6f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!br6f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png 424w, https://substackcdn.com/image/fetch/$s_!br6f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png 848w, https://substackcdn.com/image/fetch/$s_!br6f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png 1272w, https://substackcdn.com/image/fetch/$s_!br6f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!br6f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png" width="1403" height="444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:444,&quot;width&quot;:1403,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:45401,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/178015433?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!br6f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png 424w, https://substackcdn.com/image/fetch/$s_!br6f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png 848w, https://substackcdn.com/image/fetch/$s_!br6f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png 1272w, https://substackcdn.com/image/fetch/$s_!br6f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F133cf08d-368c-48cb-ae41-ec4725f4c2e4_1403x444.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Configure the gRPC Request Interface</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gWfG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gWfG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png 424w, https://substackcdn.com/image/fetch/$s_!gWfG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png 848w, https://substackcdn.com/image/fetch/$s_!gWfG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png 1272w, https://substackcdn.com/image/fetch/$s_!gWfG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gWfG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png" width="1350" height="444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:444,&quot;width&quot;:1350,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50518,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/178015433?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gWfG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png 424w, https://substackcdn.com/image/fetch/$s_!gWfG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png 848w, https://substackcdn.com/image/fetch/$s_!gWfG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png 1272w, https://substackcdn.com/image/fetch/$s_!gWfG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23502804-f471-40fd-8e41-db1347dc8e25_1350x444.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Verify Successful Import</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2UQo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2UQo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png 424w, https://substackcdn.com/image/fetch/$s_!2UQo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png 848w, https://substackcdn.com/image/fetch/$s_!2UQo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png 1272w, https://substackcdn.com/image/fetch/$s_!2UQo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2UQo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png" width="1379" height="479" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:479,&quot;width&quot;:1379,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:53822,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/178015433?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2UQo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png 424w, https://substackcdn.com/image/fetch/$s_!2UQo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png 848w, https://substackcdn.com/image/fetch/$s_!2UQo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png 1272w, https://substackcdn.com/image/fetch/$s_!2UQo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e4745e-0e8d-4eae-92e9-c5f44d91b878_1379x479.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Test Create Product Request</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!v46Q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!v46Q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png 424w, https://substackcdn.com/image/fetch/$s_!v46Q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png 848w, https://substackcdn.com/image/fetch/$s_!v46Q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png 1272w, https://substackcdn.com/image/fetch/$s_!v46Q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!v46Q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png" width="1375" height="361" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:361,&quot;width&quot;:1375,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:36943,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/178015433?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!v46Q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png 424w, https://substackcdn.com/image/fetch/$s_!v46Q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png 848w, https://substackcdn.com/image/fetch/$s_!v46Q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png 1272w, https://substackcdn.com/image/fetch/$s_!v46Q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F650808f9-177a-4fd2-b1f5-f4f6c9857609_1375x361.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Output:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NgRA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NgRA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png 424w, https://substackcdn.com/image/fetch/$s_!NgRA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png 848w, https://substackcdn.com/image/fetch/$s_!NgRA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png 1272w, https://substackcdn.com/image/fetch/$s_!NgRA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NgRA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png" width="1331" height="466" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:466,&quot;width&quot;:1331,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:45343,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/178015433?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NgRA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png 424w, https://substackcdn.com/image/fetch/$s_!NgRA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png 848w, https://substackcdn.com/image/fetch/$s_!NgRA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png 1272w, https://substackcdn.com/image/fetch/$s_!NgRA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86e03cc5-7859-4c97-8701-8190a8352675_1331x466.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>&#129473; What You Learned</h2><p>By now, we&#8217;ve covered:</p><ul><li><p>Creating Proto contracts</p></li><li><p>Generating gRPC server-side code</p></li><li><p>Implementing CRUD operations</p></li><li><p>Using EF Core for persistence</p></li></ul><p><strong>This template can easily evolve into:</strong></p><ul><li><p>Event-driven microservices</p></li><li><p>Inventory platforms</p></li><li><p>Catalog services</p></li><li><p>SaaS product management</p></li></ul><h2>&#128293; Final Thoughts</h2><p>gRPC isn&#8217;t here to replace REST.<br>Instead, it solves <strong>internal service communication</strong> where:</p><ul><li><p>Low latency matters</p></li><li><p>High throughput is required</p></li><li><p>Contract safety reduces regressions</p></li></ul><p>Combine this with streaming,<br>and gRPC becomes a serious toolkit for modern architectures.</p><h2>&#128172; <strong>Are you using gRPC in production?</strong></h2><p>What&#8217;s the biggest benefit you&#8217;ve seen?</p><p>Comment below &#128071;</p><div><hr></div><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="http://sourcecode.kanaiyakatarmal.com/GRPC">http://sourcecode.kanaiyakatarmal.com/GRPC</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Mastering AES-256 Encryption and Decryption in C#]]></title><description><![CDATA[Securing Sensitive Data Made Simple]]></description><link>https://newsletter.kanaiyakatarmal.com/p/mastering-aes-256-encryption-and-decryption</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/mastering-aes-256-encryption-and-decryption</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Tue, 11 Nov 2025 04:31:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!yKsU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Data security is no longer optional &#8212; whether you&#8217;re protecting user credentials, API keys, or confidential configuration files.<br>In .NET and C#, one of the most reliable ways to protect data at rest or in transit is by using <strong>AES-256 encryption</strong> &#8212; a symmetric encryption standard trusted worldwide for its speed and strength.</p><p>In this guide, we&#8217;ll explore:</p><ul><li><p>&#128312; What AES-256 is and why it matters</p></li><li><p>&#128312; How to implement AES-256 encryption and decryption in C#</p></li><li><p>&#128312; Practical tips for using it securely in real-world apps</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yKsU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yKsU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png 424w, https://substackcdn.com/image/fetch/$s_!yKsU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png 848w, https://substackcdn.com/image/fetch/$s_!yKsU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png 1272w, https://substackcdn.com/image/fetch/$s_!yKsU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yKsU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png" width="1299" height="701" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:701,&quot;width&quot;:1299,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:299412,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/176574974?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yKsU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png 424w, https://substackcdn.com/image/fetch/$s_!yKsU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png 848w, https://substackcdn.com/image/fetch/$s_!yKsU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png 1272w, https://substackcdn.com/image/fetch/$s_!yKsU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb42b47f1-0372-445a-b98c-686afba9d3f7_1299x701.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>&#129513; What is AES-256?</h2><p><strong>AES (Advanced Encryption Standard)</strong> is a symmetric encryption algorithm &#8212; meaning the same key is used to both encrypt and decrypt data.</p><p>The &#8220;256&#8221; in <strong>AES-256</strong> refers to the <strong>key size</strong> &#8212; a 256-bit key provides <strong>2&#178;&#8309;&#8310; possible combinations</strong>, making brute-force attacks virtually impossible.</p><p>It&#8217;s widely used in:</p><ul><li><p>Secure messaging apps</p></li><li><p>Encrypted databases</p></li><li><p>File and password storage</p></li><li><p>Network communications (TLS, VPNs, etc.)</p></li></ul><h2>&#129513; What is AES-256?</h2><p><strong>AES (Advanced Encryption Standard)</strong> is a symmetric encryption algorithm &#8212; meaning the same key is used to both encrypt and decrypt data.</p><p>The &#8220;256&#8221; in <strong>AES-256</strong> refers to the <strong>key size</strong> &#8212; a 256-bit key provides <strong>2&#178;&#8309;&#8310; possible combinations</strong>, making brute-force attacks virtually impossible.</p><p>It&#8217;s widely used in:</p><ul><li><p>Secure messaging apps</p></li><li><p>Encrypted databases</p></li><li><p>File and password storage</p></li><li><p>Network communications (TLS, VPNs, etc.)</p></li></ul><h2>&#9881;&#65039; Implementing AES-256 Encryption in C#</h2><p>C# makes encryption straightforward through the <code>System.Security.Cryptography</code> namespace.<br>Let&#8217;s implement a <strong>helper class</strong> that can encrypt and decrypt strings using AES-256.</p><p></p><p><strong>&#128295; Full Code Example</strong></p><pre><code>using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public static class AesEncryptionHelper
{
    // Encrypt a plain text string using AES-256
    public static string Encrypt(string plainText, string key)
    {
        using Aes aes = Aes.Create();
        aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key)); // Generate a 256-bit key
        aes.GenerateIV(); // Create a random IV for each encryption

        using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        using var ms = new MemoryStream();
        ms.Write(aes.IV, 0, aes.IV.Length); // Prepend IV to ciphertext

        using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
        using (var writer = new StreamWriter(cs))
        {
            writer.Write(plainText);
        }

        return Convert.ToBase64String(ms.ToArray());
    }

    // Decrypt a cipher text string using AES-256
    public static string Decrypt(string cipherText, string key)
    {
        byte[] fullCipher = Convert.FromBase64String(cipherText);
        using Aes aes = Aes.Create();
        aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key));

        // Extract IV from the beginning
        byte[] iv = new byte[aes.BlockSize / 8];
        Array.Copy(fullCipher, iv, iv.Length);

        int cipherStartIndex = iv.Length;
        int cipherLength = fullCipher.Length - cipherStartIndex;

        using var decryptor = aes.CreateDecryptor(aes.Key, iv);
        using var ms = new MemoryStream(fullCipher, cipherStartIndex, cipherLength);
        using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
        using var reader = new StreamReader(cs);
        return reader.ReadToEnd();
    }
}</code></pre><p><strong>&#129504; Example Usage</strong></p><pre><code>string secretKey = "MySuperSecretKey@123";
string plainText = "Hello AES 256 Encryption!";

string encrypted = AesEncryptionHelper.Encrypt(plainText, secretKey);
Console.WriteLine($"&#128274; Encrypted: {encrypted}");

string decrypted = AesEncryptionHelper.Decrypt(encrypted, secretKey);
Console.WriteLine($"&#128275; Decrypted: {decrypted}");</code></pre><h2>&#129513; How It Works</h2><ol><li><p><strong>Key Derivation:</strong><br>We use <code>SHA256.HashData()</code> to convert the input key into a 256-bit key required by AES-256.</p></li><li><p><strong>IV (Initialization Vector):</strong><br>A random IV is generated each time to ensure the same plain text never produces the same cipher text.</p></li><li><p><strong>CryptoStream:</strong><br>Handles the heavy lifting of encrypting and decrypting data streams securely.</p></li><li><p><strong>Base64 Encoding:</strong><br>Converts binary data to text &#8212; making it easy to store or transmit over APIs, JSON, or databases.</p></li></ol><p></p><h2>&#129521; Best Practices for AES-256 in .NET</h2><p>&#9989; <strong>Use a strong, random key</strong> &#8212; don&#8217;t hard-code it in your source code.<br>&#9989; <strong>Always use a unique IV</strong> for every encryption (as shown above).<br>&#9989; <strong>Store key and IV securely</strong> (e.g., Azure Key Vault, AWS KMS, or environment variables).<br>&#9989; <strong>Prefer AES-GCM mode</strong> for modern applications if authenticated encryption (integrity + confidentiality) is needed.<br>&#9989; <strong>Avoid reusing encryption logic for passwords</strong> &#8212; use hashing algorithms like <code>PBKDF2</code>, <code>bcrypt</code>, or <code>Argon2</code> instead.</p><p></p><h2>&#129534; When to Use AES-256</h2><p>Use AES-256 when:</p><ul><li><p>You need to <strong>store confidential configuration data</strong> (connection strings, API keys).</p></li><li><p>You must <strong>encrypt local files or backups</strong>.</p></li><li><p>You&#8217;re <strong>transmitting sensitive data</strong> between services without TLS.</p></li></ul><p></p><h2>&#127937; Wrapping Up</h2><p>AES-256 provides a powerful, efficient, and secure way to encrypt data in C#.<br>With just a few lines of code, you can protect your application from data leaks and unauthorized access.</p><blockquote><p>&#128172; How are you currently handling encryption in your .NET applications?<br>Have you tried implementing AES-256 or another cipher?</p></blockquote><h2></h2><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/AesEncryptionSample">https://sourcecode.kanaiyakatarmal.com/AesEncryptionSample</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Class vs Record vs Struct in C#: Understanding the Differences]]></title><description><![CDATA[Explore how Class, Record, and Struct differ in C# and learn when to use each for better performance and design.]]></description><link>https://newsletter.kanaiyakatarmal.com/p/class-vs-record-vs-struct-in-c-understanding</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/class-vs-record-vs-struct-in-c-understanding</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Mon, 03 Nov 2025 04:31:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!3JSV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>C# has evolved a lot over the years, and with C# 9 and 10, <strong>records</strong> and improved <strong>structs</strong> bring new options to model your data. Choosing the right type is essential for performance, maintainability, and clarity of your code. Let&#8217;s dive into the differences between <strong>Class</strong>, <strong>Record</strong>, and <strong>Struct</strong> in C# with examples.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3JSV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3JSV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png 424w, https://substackcdn.com/image/fetch/$s_!3JSV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png 848w, https://substackcdn.com/image/fetch/$s_!3JSV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png 1272w, https://substackcdn.com/image/fetch/$s_!3JSV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3JSV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png" width="1031" height="577" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/feef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:577,&quot;width&quot;:1031,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59984,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/176565012?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3JSV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png 424w, https://substackcdn.com/image/fetch/$s_!3JSV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png 848w, https://substackcdn.com/image/fetch/$s_!3JSV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png 1272w, https://substackcdn.com/image/fetch/$s_!3JSV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffeef1c54-e22e-4581-9c97-5c56a9eb5d6f_1031x577.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>1. Class</h2><p>A <strong>class</strong> in C# is a <strong>reference type</strong>, meaning it is stored on the <strong>heap</strong>, and assignments create references to the same object. Classes support inheritance, encapsulation, and polymorphism.</p><h3>Example</h3><pre><code>public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

// Usage
var person1 = new Person("Alice", 25);
var person2 = person1;
person2.Age = 30;

Console.WriteLine(person1.Age); // Output: 30</code></pre><p><strong>Key Points:</strong></p><ul><li><p>Reference type &#8594; multiple variables can reference the same object.</p></li><li><p>Mutable by default.</p></li><li><p>Supports inheritance and polymorphism.</p></li><li><p>Good for large, complex objects with behavior.</p></li></ul><p></p><h2>2. Record</h2><p>A <strong>record</strong> is a <strong>reference type</strong> designed for <strong>immutable data models</strong>. Records are great for <strong>data-centric objects</strong>, like DTOs or configuration settings, and support <strong>value-based equality</strong> out of the box.</p><h3>Example</h3><pre><code>public record PersonRecord(string Name, int Age);

// Usage
var record1 = new PersonRecord("Bob", 30);
var record2 = record1 with { Age = 35 };

Console.WriteLine(record1.Age); // Output: 30
Console.WriteLine(record2.Age); // Output: 35</code></pre><p><strong>Key Points:</strong></p><ul><li><p>Immutable by default (though you can define mutable properties).</p></li><li><p>Supports value equality (<code>Equals</code> and <code>GetHashCode</code> based on properties).</p></li><li><p>Ideal for immutable DTOs, event data, or simple models.</p></li><li><p>Supports <code>with</code> expressions for easy copying with changes.</p></li></ul><h2>3. Struct</h2><p>A <strong>struct</strong> in C# is a <strong>value type</strong>, stored on the <strong>stack</strong> (or inline in arrays). Assignments copy the value, making structs lightweight and suitable for performance-critical scenarios.</p><pre><code>public struct PersonStruct
{
    public string Name { get; set; }
    public int Age { get; set; }

    public PersonStruct(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

// Usage
var struct1 = new PersonStruct("Charlie", 28);
var struct2 = struct1;
struct2.Age = 35;

Console.WriteLine(struct1.Age); // Output: 28</code></pre><p><strong>Key Points:</strong></p><ul><li><p>Value type &#8594; copying creates a new independent object.</p></li><li><p>Lightweight, good for small data structures.</p></li><li><p>Cannot have inheritance.</p></li><li><p>Best for performance-sensitive, small, immutable objects.</p></li></ul><h2>When to Use What?</h2><p>Choosing between <strong>class</strong>, <strong>record</strong>, and <strong>struct</strong> depends on the nature of your data and how you intend to use it. Use a <strong>class</strong> when you have complex objects that are mutable, require identity, or need behavior such as methods, inheritance, or polymorphism. Classes are great for large, behavior-rich entities but come with heap allocation and garbage collection overhead.</p><p>Use a <strong>record</strong> when your focus is on <strong>immutable data</strong> and <strong>value-based equality</strong>. Records are ideal for data transfer objects (DTOs), configuration settings, or event data where the content matters more than the object identity. They offer concise syntax and built-in equality comparison, making it easy to work with immutable models.</p><p>Use a <strong>struct</strong> for <strong>small, lightweight objects</strong> where performance is critical. Structs are value types and are stored on the stack, so copying creates independent copies without heap allocation. They are perfect for small data structures such as points, coordinates, or other numeric containers. However, avoid large structs or structs that require inheritance, as this can lead to performance issues or unnecessary complexity.</p><p><strong>Rule of thumb:</strong> choose a class for objects with behavior, a record for immutable data, and a struct for small, high-performance value objects.</p><p></p><h2>Conclusion</h2><p>Understanding the difference between <strong>class</strong>, <strong>record</strong>, and <strong>struct</strong> helps you write <strong>cleaner, efficient, and maintainable C# code</strong>. Choosing the right type depends on <strong>mutability, size, and identity requirements</strong> of your objects.</p><ul><li><p><strong>Class</strong> &#8594; mutable, identity-focused, behavior-rich.</p></li><li><p><strong>Record</strong> &#8594; immutable, data-centric, value-based equality.</p></li><li><p><strong>Struct</strong> &#8594; lightweight, value-focused, high-performance.</p></li></ul><p>Choosing wisely can improve <strong>performance</strong>, <strong>readability</strong>, and reduce subtle bugs related to object copying or equality.</p><p></p><h2>&#128313; Example Summary</h2><pre><code>Person person = new("Alice", 25);          // Class
PersonRecord record = new("Bob", 30);      // Record
PersonStruct pStruct = new("Charlie", 28); // Struct</code></pre><p>&#128161; <strong>Question for you:</strong> Which one do you find yourself using most often in your C# projects, and why?</p><p></p><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/ClassvsRecordvsStruct">https://sourcecode.kanaiyakatarmal.com/ClassvsRecordvsStruct</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How to Use IExceptionHandler to Handle Exceptions in .NET 8]]></title><description><![CDATA[Learn how to handle exceptions globally in .NET 8. Implement IExceptionHandler to return clean, consistent API responses.]]></description><link>https://newsletter.kanaiyakatarmal.com/p/how-to-use-iexceptionhandler-to-handle</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/how-to-use-iexceptionhandler-to-handle</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Tue, 28 Oct 2025 04:31:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!jdS2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Exception handling is a critical aspect of building robust and reliable web applications. In .NET 8, Microsoft introduced a new, more elegant way to handle exceptions globally using the <code>IExceptionHandler</code> interface. This approach provides a clean, centralized way to manage exceptions across your entire application.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jdS2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jdS2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png 424w, https://substackcdn.com/image/fetch/$s_!jdS2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png 848w, https://substackcdn.com/image/fetch/$s_!jdS2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png 1272w, https://substackcdn.com/image/fetch/$s_!jdS2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jdS2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png" width="1036" height="580" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:580,&quot;width&quot;:1036,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:61745,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/176492826?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jdS2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png 424w, https://substackcdn.com/image/fetch/$s_!jdS2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png 848w, https://substackcdn.com/image/fetch/$s_!jdS2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png 1272w, https://substackcdn.com/image/fetch/$s_!jdS2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ec3857f-18b4-4c92-80bb-739ed252e4cf_1036x580.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>&#128679; The Problem with Traditional Exception Handling</h2><p>Before .NET 8, developers typically relied on <strong>middleware</strong> or <strong>filters</strong> to handle exceptions globally.<br>While those approaches worked, they often led to:</p><ul><li><p>&#9881;&#65039; Complex middleware configurations</p></li><li><p>&#128230; Scattered exception handling logic</p></li><li><p>&#129514; Difficulty in testing</p></li><li><p>&#9888;&#65039; Inconsistent or unclear error responses</p></li></ul><p>As projects scaled, maintaining a consistent global error strategy became a real challenge.</p><h2>&#128161; Introducing <code>IExceptionHandler</code></h2><p>The <code>IExceptionHandler</code> interface, part of the <code>Microsoft.AspNetCore.Diagnostics</code> namespace, offers a <strong>modern and standardized</strong> solution.</p><h3>&#128273; Key Benefits:</h3><p>&#9989; <strong>Centralized handling</strong> &#8211; All exceptions flow through a single pipeline<br>&#129513; <strong>Flexibility</strong> &#8211; Multiple handlers can be registered for specific exception types<br>&#129514; <strong>Testability</strong> &#8211; Handlers are easier to unit test in isolation<br>&#127919; <strong>Consistency</strong> &#8211; Uniform error responses across your API</p><p>This aligns perfectly with the <strong>Clean Architecture</strong> principle of separation of concerns &#8212; where exception management is isolated from business logic.</p><h2><strong>Setting Up the Project</strong></h2><p>Let&#8217;s start by creating a new Web API project and configuring global exception handling.</p><h3><strong>1. Project Configuration</strong></h3><p>Having set up our project, let&#8217;s now add the <code>GlobalExceptionHandler</code> class which implements <code>IExceptionHandler</code>:</p><pre><code>public class CustomExceptionHandler: IExceptionHandler
{
}</code></pre><p>Then, let&#8217;s register our exception handler in the dependency injection container:</p><pre><code>builder.Services.AddExceptionHandler&lt;CustomExceptionHandler&gt;();
builder.Services.AddProblemDetails();</code></pre><p>Whenever there&#8217;s an exception in our application, the <code>GlobalExceptionHandler</code> handler will be automatically invoked. The API will also generate <code>ProblemDetails</code> standardized responses as per <strong><a href="https://datatracker.ietf.org/doc/html/rfc7807">RFC 7807 specification</a></strong>. This way, we have more control over how we intercept, handle, and format error responses.</p><p>Finally, let&#8217;s add the middleware to the application request pipeline:</p><pre><code>app.UseExceptionHandler<strong>()</strong>;</code></pre><p>Now, let&#8217;s proceed to handle exceptions.</p><p>Let&#8217;s add a custom implementation of <code>TryHandleAsync()</code> method in our middleware:</p><pre><code> public async ValueTask&lt;bool&gt; TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
 {
     int statusCode = exception switch
     {
         UnauthorizedAccessException =&gt; StatusCodes.Status401Unauthorized,
         ArgumentException =&gt; StatusCodes.Status400BadRequest,
         _ =&gt; StatusCodes.Status500InternalServerError
     };
     context.Response.StatusCode = statusCode;

     var problemDetails = new ProblemDetails
     {
         Status = statusCode,
         Title = &#8220;An error occurred&#8221;,
         Type = exception.GetType().Name,
         Detail = exception.Message,
         Instance = context.Request.Path
     };

     await context.Response.WriteAsJsonAsync(problemDetails, cancellationToken);

     return true;
 }</code></pre><p>Let&#8217;s add an endpoint in the <code>Program</code> class to test this:</p><pre><code>app.MapGet("/", () =&gt; "Hello World!");


// Example endpoint that throws an exception
app.MapGet("/error", () =&gt;
{
    throw new Exception("This is a test exception!");
});</code></pre><p>Calling the endpoint, the application throws a <code>Exception</code> exception.</p><p>Let&#8217;s test this out:</p><pre><code>{
  "type": "Exception",
  "title": "An error occurred",
  "status": 500,
  "detail": "This is a test exception!",
  "instance": "/error"
}</code></pre><h2>&#127937; Conclusion</h2><p>The <code>IExceptionHandler</code> interface in .NET 8 provides a <strong>clean, testable, and extensible</strong> foundation for global exception handling.</p><p>By implementing it, you can:</p><ul><li><p>Centralize and simplify your error handling logic</p></li><li><p>Deliver consistent, secure, and meaningful error responses</p></li><li><p>Easily extend handling for specific exception types</p></li><li><p>Improve maintainability, readability, and test coverage</p></li></ul><p>In short, this feature is a <strong>game-changer for API resilience</strong> and a <strong>step toward cleaner architecture</strong> in modern .NET applications.</p><div><hr></div><h3>&#128172; Final Thought</h3><p>If you haven&#8217;t yet tried <code>IExceptionHandler</code>, it&#8217;s a great opportunity to refactor your error handling strategy and align with the <strong>best practices of .NET 8</strong>.<br>A little investment in this pattern goes a long way in improving reliability and developer experience.</p><p></p><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/GlobalErrorHandling">https://sourcecode.kanaiyakatarmal.com/GlobalErrorHandling</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Understanding the Adapter Design Pattern in C#]]></title><description><![CDATA[Bridging incompatible interfaces with the power of design the Adapter Pattern in C#]]></description><link>https://newsletter.kanaiyakatarmal.com/p/understanding-the-adapter-design-pattern</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/understanding-the-adapter-design-pattern</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Thu, 23 Oct 2025 04:31:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_ky1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In modern software development, we often encounter situations where two incompatible interfaces need to work together. The <strong>Adapter Design Pattern</strong> provides a solution for this by acting as a bridge between these incompatible interfaces. It is widely used in C# and other object-oriented languages to enhance code flexibility and maintainability.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_ky1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_ky1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png 424w, https://substackcdn.com/image/fetch/$s_!_ky1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png 848w, https://substackcdn.com/image/fetch/$s_!_ky1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png 1272w, https://substackcdn.com/image/fetch/$s_!_ky1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_ky1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png" width="870" height="444" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:444,&quot;width&quot;:870,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:500994,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/176358795?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_ky1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png 424w, https://substackcdn.com/image/fetch/$s_!_ky1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png 848w, https://substackcdn.com/image/fetch/$s_!_ky1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png 1272w, https://substackcdn.com/image/fetch/$s_!_ky1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c7f531f-0ab9-49bb-a7b3-03beaab48357_870x444.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>What is the Adapter Design Pattern?</h2><p>The <strong>Adapter Pattern</strong> is a <strong>structural design pattern</strong> that allows objects with incompatible interfaces to collaborate. Essentially, it converts the interface of a class into another interface that the client expects.</p><ul><li><p><strong>Purpose:</strong> Allow incompatible interfaces to work together without changing their source code.</p></li><li><p><strong>Also Known As:</strong> Wrapper.</p></li><li><p><strong>Category:</strong> Structural pattern.</p></li></ul><p></p><h2>When to Use the Adapter Pattern</h2><p>Use the Adapter Pattern when:</p><ol><li><p>You want to integrate a class with an existing system but its interface is incompatible.</p></li><li><p>You want to reuse an existing class but need to adapt it to a new interface.</p></li><li><p>You aim to decouple code and make it more flexible and maintainable.</p></li></ol><p></p><h2>Structure of Adapter Pattern</h2><p>The Adapter Pattern generally involves the following components:</p><ol><li><p><strong>Target</strong> &#8211; Defines the domain-specific interface that the client uses.</p></li><li><p><strong>Client</strong> &#8211; Collaborates with objects conforming to the Target interface.</p></li><li><p><strong>Adaptee</strong> &#8211; The existing class with an incompatible interface.</p></li><li><p><strong>Adapter</strong> &#8211; Converts the interface of the Adaptee into the Target interface expected by the Client.</p></li></ol><p></p><pre><code>Client &#8594; Target &#8594; Adapter &#8594; Adaptee</code></pre><h2>C# Example of Adapter Pattern</h2><p>Imagine we have an old <strong>legacy logging system</strong> and a new system that expects a different interface.</p><h3>Step 1: Define the Target Interface</h3><pre><code>public interface ILogger
{
    void Log(string message);
}</code></pre><h3>Step 2: Existing Class (Adaptee)</h3><pre><code>public class LegacyLogger
{
    public void WriteLog(string logMessage)
    {
        Console.WriteLine($&#8221;Legacy log: {logMessage}&#8221;);
    }
}</code></pre><h3>Step 3: Create the Adapter</h3><pre><code>public class LoggerAdapter : ILogger
{
    private readonly LegacyLogger _legacyLogger;

    public LoggerAdapter(LegacyLogger legacyLogger)
    {
        _legacyLogger = legacyLogger;
    }

    public void Log(string message)
    {
        _legacyLogger.WriteLog(message); // Adapted call
    }
}</code></pre><h3>Step 4: Using the Adapter</h3><pre><code>class Program
{
    static void Main(string[] args)
    {
        LegacyLogger legacyLogger = new LegacyLogger();
        ILogger logger = new LoggerAdapter(legacyLogger);

        logger.Log(&#8221;Adapter pattern in action!&#8221;);
    }
}</code></pre><p>Output:</p><pre><code>Legacy log: Adapter pattern in action!</code></pre><p>Here, the <code>LoggerAdapter</code> bridges the gap between the <code>ILogger</code> interface and the <code>LegacyLogger</code> class.</p><p></p><h2>Benefits of Adapter Pattern</h2><ul><li><p>Promotes <strong>code reuse</strong> without modifying existing code.</p></li><li><p>Enhances <strong>flexibility</strong> by decoupling client code from specific implementations.</p></li><li><p>Makes it easier to integrate <strong>third-party libraries</strong>.</p></li></ul><p></p><h2>Real-World Examples</h2><ol><li><p>Connecting <strong>new payment gateways</strong> to legacy e-commerce systems.</p></li><li><p>Adapting <strong>different file format readers</strong> in a document management system.</p></li><li><p>Bridging <strong>old APIs with modern service-oriented architectures</strong>.</p></li></ol><h2>Conclusion</h2><p>The <strong>Adapter Design Pattern</strong> is a simple yet powerful solution to interface incompatibility problems. By using it in C#, you can integrate legacy systems, third-party libraries, or any incompatible components seamlessly while keeping your code clean and maintainable.</p><p></p><p>&#128172; <em>Have you ever used the Adapter Pattern in a real project? What challenges did it help you solve?</em></p><div><hr></div><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/AdapterDesignPattern">https://sourcecode.kanaiyakatarmal.com/AdapterDesignPattern</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[🧠 Caching in .NET the Right Way — Layers, Patterns & Real-World Code]]></title><description><![CDATA[Discover how to design efficient caching strategies to boost performance and scalability in your .NET apps.]]></description><link>https://newsletter.kanaiyakatarmal.com/p/designing-efficient-cache-layers</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/designing-efficient-cache-layers</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Mon, 20 Oct 2025 04:31:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!KlIq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Caching correctly can make your .NET app dramatically faster and far more scalable  but done poorly it creates bugs, stale data, and operational headaches. This article walks you through practical caching layers, patterns, code samples, and operational advice so you can design a robust caching strategy for real-world .NET systems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KlIq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KlIq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png 424w, https://substackcdn.com/image/fetch/$s_!KlIq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png 848w, https://substackcdn.com/image/fetch/$s_!KlIq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png 1272w, https://substackcdn.com/image/fetch/$s_!KlIq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KlIq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png" width="1404" height="1319" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1319,&quot;width&quot;:1404,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:351964,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/176385168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KlIq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png 424w, https://substackcdn.com/image/fetch/$s_!KlIq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png 848w, https://substackcdn.com/image/fetch/$s_!KlIq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png 1272w, https://substackcdn.com/image/fetch/$s_!KlIq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f576d26-22d4-4190-8b89-c24ac5d08771_1404x1319.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>&#9889; TL;DR (Two-line summary)</h2><p>Use a layered approach:<br>&#10145; <strong>In-process cache</strong> for ultra-fast reads<br>&#10145; <strong>Distributed cache (Redis)</strong> for shared scale<br>&#10145; <strong>Versioning + invalidation + observability</strong> for correctness and reliability</p><h2>1. Why Multi-layer Caching?</h2><p>Caching isn&#8217;t one-size-fits-all. Different layers serve different needs.</p><ul><li><p>&#128336; <strong>Latency:</strong> In-process caches (<code>IMemoryCache</code>) return data in microseconds.</p></li><li><p>&#127757; <strong>Scale &amp; consistency:</strong> Distributed caches (Redis) let multiple app instances share state.</p></li><li><p>&#128176; <strong>Cost:</strong> Cache hot paths, not everything &#8212; protect the DB from expensive reads.</p></li><li><p>&#9881;&#65039; <strong>Flexibility:</strong> Different access patterns require different cache semantics.</p></li></ul><p></p><h2>2. Cache layers roles &amp; choices</h2><h2>a) In-process (local) cache</h2><ul><li><p>Use for per-instance hot items (computed values, config).</p></li><li><p>.NET: <code>IMemoryCache</code> (<code>Microsoft.Extensions.Caching.Memory</code>).</p></li><li><p>Fast, but not shared across instances.</p></li></ul><h2>b) Distributed cache</h2><ul><li><p>Use for shared data between instances (user sessions, product catalogs).</p></li><li><p>Options: Redis (common), Memcached (less common for modern .NET), Azure Cache for Redis.</p></li><li><p>.NET: <code>IDistributedCache</code> abstraction or direct client (StackExchange.Redis) for advanced scenarios.</p></li></ul><h2>c) CDN / edge caches</h2><ul><li><p>For static assets and HTTP responses (images, HTML fragments).</p></li><li><p>Useful for public-facing read-heavy endpoints.</p></li></ul><h2>d) Application-level output/response caching</h2><ul><li><p>Use Response Caching, Output Caching or Varnish in front.</p></li><li><p>In .NET: <code>ResponseCaching</code> middleware or ASP.NET Core Output Caching.</p></li></ul><p></p><h2>3. Caching Patterns in Action</h2><p>Let&#8217;s use the new <code>HybridCache</code> introduced in .NET 8+ to illustrate core caching patterns.</p><p>Here&#8217;s a simple setup:</p><pre><code>public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}</code></pre><pre><code>public class ProductRepository
{
    private readonly Dictionary&lt;int, Product&gt; _db = new();

    public ProductRepository()
    {
        _db[1] = new Product { Id = 1, Name = &#8220;Laptop&#8221;, Price = 1200 };
        _db[2] = new Product { Id = 2, Name = &#8220;Phone&#8221;, Price = 800 };
    }

    public Task&lt;Product?&gt; GetByIdAsync(int id)
    {
        _db.TryGetValue(id, out var product);
        Console.WriteLine($&#8221;[DB] Get Product {id}&#8221;);
        return Task.FromResult(product);
    }

    public Task UpdateAsync(Product product)
    {
        _db[product.Id] = product;
        Console.WriteLine($&#8221;[DB] Updated Product {product.Id}&#8221;);
        return Task.CompletedTask;
    }
}
</code></pre><p>Cache Service</p><pre><code>public class HybridCacheService
{
    private readonly HybridCache cache;
    private readonly ProductRepository repository;

    public HybridCacheService(HybridCache cache, ProductRepository repository)
    {
        this.cache = cache;
        this.repository = repository;
    }

    #region 1. Cache-Aside (Lazy Loading)
    public async Task&lt;Product?&gt; GetProductCacheAsideAsync(int id)
    {
        string cacheKey = $&#8221;Product_{id}&#8221;;

        return await cache.GetOrCreateAsync(
            cacheKey,
            async token =&gt; await repository.GetByIdAsync(id),
            new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(10) });
    }
    #endregion

    #region 2. Read-Through
    public async Task&lt;Product?&gt; GetProductReadThroughAsync(int id)
    {
        string cacheKey = $&#8221;Product_{id}&#8221;;

        return await cache.GetOrCreateAsync(
            cacheKey,
            async token =&gt; await repository.GetByIdAsync(id),
            new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(10) });
    }
    #endregion

    #region 3. Write-Around
    public async Task UpdateProductWriteAroundAsync(Product product)
    {
        // Write directly to DB only
        await repository.UpdateAsync(product);

        // Cache not updated
        Console.WriteLine(&#8221;[Cache-Write-Around] Skipped cache update&#8221;);
    }
    #endregion

    #region 4. Write-Back (Write-Behind)
    public async Task UpdateProductWriteBackAsync(Product product)
    {
        string cacheKey = $&#8221;Product_{product.Id}&#8221;;

        // Update cache first
        await cache.SetAsync(cacheKey, product, new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(10)
        });

        Console.WriteLine(&#8221;[Cache-Write-Back] Updated cache, DB async&#8221;);

        // Simulate background DB update
        _ = Task.Run(async () =&gt; await repository.UpdateAsync(product));
    }
    #endregion

    #region 5. Write-Through
    public async Task UpdateProductWriteThroughAsync(Product product)
    {
        string cacheKey = $&#8221;Product_{product.Id}&#8221;;

        // Update DB first
        await repository.UpdateAsync(product);

        // Update cache immediately
        await cache.SetAsync(cacheKey, product, new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(10)
        });

        Console.WriteLine(&#8221;[Cache-Write-Through] Updated cache &amp; DB&#8221;);
    }
    #endregion
}</code></pre><h1>4. Caching patterns (when to use which)</h1><h3>Cache-Aside (lazy loading)</h3><ol><li><p>App checks cache.</p></li><li><p>On miss, load from DB, store in cache, then return.</p></li></ol><ul><li><p>Pros: simple, explicit control.</p></li><li><p>Cons: potential stampede on high-concurrency misses.</p></li></ul><h3>Read-Through / Write-Through</h3><ul><li><p>Cache client (or a library) automatically fetches or writes through to backing store.</p></li><li><p>Better for centralizing caching logic; often used in distributed caches with middleware.</p></li></ul><h3>Write-Behind (async write-back)</h3><ul><li><p>Writes first to cache, then asynchronously to DB.</p></li><li><p>Use cautiously: risk of data loss on crashes.</p></li></ul><h3>Write-Around Caching</h3><ul><li><p>Avoid polluting the cache with data that may not be read soon after writing.</p></li></ul><h1>4. Consistency &amp; Invalidation Strategies</h1><p>Consistency is often harder than caching itself. Choose your invalidation model wisely:</p><ul><li><p>&#9200; <strong>Time-based TTL:</strong> Simple expiration; best for mostly-read or eventually consistent data.</p></li><li><p>&#128260; <strong>Event-driven invalidation:</strong> Publish Redis Pub/Sub or message-bus events on data change.</p></li><li><p>&#129513; <strong>Versioned keys:</strong> Add a version suffix &#8212; <code>product:123:v42</code> &#8212; to invalidate old entries.</p></li><li><p>&#127919; <strong>Partial invalidation:</strong> Invalidate specific keys instead of global cache flushes.</p></li></ul><p></p><h1>5. Preventing Cache Stampede (a.k.a. Thundering Herd)</h1><p>When multiple requests miss the cache simultaneously:</p><ul><li><p>&#128274; Use <strong>distributed locks</strong> (e.g., Redis RedLock).</p></li><li><p>&#128678; Apply the <strong>Singleflight pattern</strong> to deduplicate concurrent requests.</p></li><li><p>&#128257; Implement <strong>in-flight caching</strong> &#8212; reuse in-progress tasks for the same key.</p></li></ul><p></p><h1>6. Key Design &amp; Serialization</h1><ul><li><p>Use <strong>predictable, readable keys</strong> like <code>product:{id}:detail:v3</code>.</p></li><li><p>Avoid embedding JSON into keys.</p></li><li><p>Use <strong>MessagePack</strong> or <strong>protobuf</strong> for large objects.</p></li><li><p>Compress only if your payloads are big enough to justify CPU cost.</p></li></ul><h1>7. Security &amp; Operational Considerations</h1><ul><li><p>&#128272; Don&#8217;t cache sensitive/PII data unless encrypted.</p></li><li><p>&#127757; Keep Redis close to your app (same region).</p></li><li><p>&#129518; Tune Redis eviction policies (<code>volatile-lru</code>, <code>allkeys-lru</code>, etc.).</p></li><li><p>&#128202; Monitor <code>IMemoryCache</code> size and Redis memory usage.</p></li><li><p>&#129504; Always track hit/miss ratios and latency metrics.</p></li></ul><h1>8. Observability &amp; Metrics</h1><p>Monitor your caching layer like a first-class system component:</p><ul><li><p>Cache hit/miss rate (overall &amp; per key group)</p></li><li><p>Average latency for <code>Get</code> and <code>Set</code> operations</p></li><li><p>Evictions and expiration events</p></li><li><p>Redis connection health and timeout rates</p></li><li><p>Export metrics to <strong>Prometheus</strong> or <strong>Application Insights</strong></p></li></ul><h1>9. Local Development &amp; Testability</h1><ul><li><p>Use in-memory cache or local Redis for testing.</p></li><li><p>Abstract caching behind an <code>ICacheService</code> interface for mocks.</p></li><li><p>Unit-test both <em>cache-hit</em> and <em>cache-miss</em> cases.</p></li></ul><p></p><h2>&#9989; Takeaways</h2><p>Caching is more than just speed &#8212; it&#8217;s about <strong>data correctness, resilience, and cost-efficiency</strong>.</p><p>&#10004; Start simple &#8212; use <code>IMemoryCache</code> for fast local reads.<br>&#10004; Add a distributed cache (Redis / HybridCache) for scale.<br>&#10004; Implement proper invalidation and observability early.<br>&#10004; Evolve your cache strategy as your traffic and data patterns grow.</p><p></p><h2>&#10067;What Do You Think?</h2><p>Caching can make or break your system&#8217;s performance - so I&#8217;m curious:</p><p>&#128073; <strong>Which caching mistake or pattern has caused you the most trouble (or biggest win) in your .NET projects?</strong></p><p>Share your thoughts or war stories in the comments, I&#8217;d love to hear how you&#8217;ve handled caching in production.</p><p></p><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/HybridCache">https://sourcecode.kanaiyakatarmal.com/HybridCache</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[🔑 Understanding Keyed Services in .NET Dependency Injection]]></title><description><![CDATA[Simplify your .NET dependency injection setup with Keyed Services the elegant new feature for managing multiple implementations effortlessly]]></description><link>https://newsletter.kanaiyakatarmal.com/p/understanding-keyed-services-in-net</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/understanding-keyed-services-in-net</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Fri, 17 Oct 2025 04:31:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!d_ff!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When working with modern .NET applications, <strong>Dependency Injection (DI)</strong> has become the standard way to manage dependencies and build modular, testable systems.</p><p>But what if you have <strong>multiple implementations of the same interface</strong> &#8212; like Email, SMS, and Push notifications &#8212; and you need to decide <strong>which one to use</strong> at runtime?</p><p>That&#8217;s where <strong>Keyed Services</strong>, introduced in <strong>.NET 8</strong>, come to the rescue.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d_ff!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d_ff!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png 424w, https://substackcdn.com/image/fetch/$s_!d_ff!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png 848w, https://substackcdn.com/image/fetch/$s_!d_ff!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png 1272w, https://substackcdn.com/image/fetch/$s_!d_ff!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d_ff!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png" width="1456" height="817" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:817,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:104896,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/176314235?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!d_ff!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png 424w, https://substackcdn.com/image/fetch/$s_!d_ff!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png 848w, https://substackcdn.com/image/fetch/$s_!d_ff!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png 1272w, https://substackcdn.com/image/fetch/$s_!d_ff!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1e521ef-f5ff-4cf4-800d-8cfb1eb2cddc_1506x845.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p></p><h2>&#129513; The Problem Before Keyed Services</h2><p>Imagine you have an interface:</p><pre><code>public interface INotificationService
{
    void Send(string message);
}</code></pre><p>And you have multiple implementations:</p><pre><code>public class EmailNotificationService : INotificationService
{
    public void Send(string message)
        =&gt; Console.WriteLine($&#8221;Email: {message}&#8221;);
}

public class SmsNotificationService : INotificationService
{
    public void Send(string message)
        =&gt; Console.WriteLine($&#8221;SMS: {message}&#8221;);
}

public class PushNotificationService : INotificationService
{
    public void Send(string message)
        =&gt; Console.WriteLine($&#8221;Push: {message}&#8221;);
}</code></pre><p>Before .NET 8, registering all these in DI meant you had to inject them as <code>IEnumerable&lt;INotificationService&gt;</code> and manually decide which one to use &#8212; not ideal.</p><div><hr></div><h2>&#128640; Enter Keyed Services</h2><p>With .NET 8, you can now register services <strong>with keys</strong> and inject them directly by key.<br>Here&#8217;s how it looks in code:</p><pre><code>builder.Services.AddKeyedSingleton&lt;INotificationService,SmsNotificationService&gt;("sms");
builder.Services.AddKeyedSingleton&lt;INotificationService,EmailNotificationService&gt;("email");
builder.Services.AddKeyedSingleton&lt;INotificationService,PushNotificationService&gt;("push");</code></pre><p>Each service registration is now associated with a unique key &#8212; <code>&#8220;sms&#8221;</code>, <code>&#8220;email&#8221;</code>, and <code>&#8220;push&#8221;</code>.</p><p></p><h2>&#128137; Injecting Keyed Services</h2><p>Once registered, you can inject the specific implementation using the <strong>[FromKeyedServices]</strong> attribute:</p><pre><code>public class NotificationHandler
{
    private readonly INotificationService _emailService;
    private readonly INotificationService _smsService;
    private readonly INotificationService _pushService;

    public NotificationHandler(
        [FromKeyedServices(&#8221;email&#8221;)] INotificationService emailService,
        [FromKeyedServices(&#8221;sms&#8221;)] INotificationService smsService,
        [FromKeyedServices(&#8221;push&#8221;)] INotificationService pushService)
    {
        _emailService = emailService;
        _smsService = smsService;
        _pushService = pushService;
    }

    public void NotifyAll()
    {
        _emailService.Send(&#8221;&#128231; Sending Email Notification&#8221;);
        _smsService.Send(&#8221;&#128241; Sending SMS Notification&#8221;);
        _pushService.Send(&#8221;&#128276; Sending Push Notification&#8221;);
    }
}</code></pre><p>No more manual filtering or loops &#8212; just clean, declarative dependency injection.</p><h2>&#9881;&#65039; Manual Resolution (Optional)</h2><p>If you ever need to resolve a keyed service <strong>manually</strong>, you can use:</p><pre><code>var smsService = provider.GetKeyedService&lt;INotificationService&gt;(&#8221;sms&#8221;);
smsService?.Send(&#8221;Manual SMS Notification&#8221;);</code></pre><p>This is useful in factories, background jobs, or middleware where constructor injection isn&#8217;t practical.</p><h2>&#128161; When to Use Keyed Services</h2><p>&#9989; You have multiple strategies or implementations (like different payment, cache, or logging providers).<br>&#9989; You want to choose implementations explicitly without complex factory logic.<br>&#9989; You prefer strong typing over string-based condition checks.</p><p></p><h2>&#9888;&#65039; When Not to Use It</h2><p>&#10060; If you only have one implementation &#8212; regular <code>AddSingleton</code> or <code>AddTransient</code> is simpler.<br>&#10060; If runtime selection depends on dynamic conditions &#8212; a <strong>factory pattern</strong> may still be more flexible.<br>&#10060; Overusing keys can make DI harder to trace &#8212; use thoughtfully.</p><p></p><h2>&#129504; Bonus: Keyed Generics</h2><p>Keyed services also support <strong>generic registrations</strong>:</p><pre><code>builder.Services.AddKeyedScoped(typeof(IRepository&lt;&gt;), typeof(SqlRepository&lt;&gt;), &#8220;sql&#8221;);
builder.Services.AddKeyedScoped(typeof(IRepository&lt;&gt;), typeof(MongoRepository&lt;&gt;), &#8220;mongo&#8221;);</code></pre><p>And you can inject them just like before:</p><pre><code>public class DataSyncService
{
    private readonly IRepository&lt;Customer&gt; _sqlRepo;

    public DataSyncService([FromKeyedServices(&#8221;sql&#8221;)] IRepository&lt;Customer&gt; sqlRepo)
    {
        _sqlRepo = sqlRepo;
    }
}</code></pre><h2>&#127937; Final Thoughts</h2><p>Keyed Services are one of the <strong>most practical additions in .NET 8&#8217;s DI system</strong>.<br>They make it easier to handle multiple implementations of the same interface &#8212; without messy filtering logic or custom factories.</p><p>So the next time you&#8217;re juggling different strategies &#8212; whether it&#8217;s <strong>Email, SMS, or Push</strong> &#8212; remember:<br><strong>A well-chosen key can make your dependencies cleaner and smarter.</strong></p><div><hr></div><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/KeyedServices">https://sourcecode.kanaiyakatarmal.com/KeyedServices</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Implementing Circuit Breaker in ASP.NET Core with Microsoft Resilience]]></title><description><![CDATA[Learn how to safeguard your services by handling transient failures with resilience.]]></description><link>https://newsletter.kanaiyakatarmal.com/p/implementing-circuit-breaker-in-aspnet</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/implementing-circuit-breaker-in-aspnet</guid><pubDate>Tue, 30 Sep 2025 04:31:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mfh1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern web applications often rely on multiple external services. But what happens when one of those services starts failing? Continuous failures can cascade through your system, slowing down or even crashing your application. This is where the <strong>Circuit Breaker</strong> pattern comes into play.</p><p>Circuit breakers protect your application from repeated failures by temporarily blocking calls to a failing service and allowing it to recover. In ASP.NET Core, you can implement this pattern seamlessly using <strong>Polly</strong> and the <code>Microsoft.Extensions.Http.Resilience</code> package.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mfh1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mfh1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png 424w, https://substackcdn.com/image/fetch/$s_!mfh1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png 848w, https://substackcdn.com/image/fetch/$s_!mfh1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png 1272w, https://substackcdn.com/image/fetch/$s_!mfh1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mfh1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png" width="923" height="452" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:452,&quot;width&quot;:923,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:135351,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/174114339?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mfh1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png 424w, https://substackcdn.com/image/fetch/$s_!mfh1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png 848w, https://substackcdn.com/image/fetch/$s_!mfh1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png 1272w, https://substackcdn.com/image/fetch/$s_!mfh1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e7c4775-792c-4029-ac03-a9b69f805329_923x452.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p></p><h2>What is a Circuit Breaker?</h2><p>The <strong>Circuit Breaker</strong> pattern is inspired by electrical circuit breakers: it prevents an application from repeatedly trying to execute an operation that is likely to fail.</p><ul><li><p><strong>Closed state:</strong> Requests flow normally.</p></li><li><p><strong>Open state:</strong> Requests fail immediately without hitting the failing service.</p></li><li><p><strong>Half-open state:</strong> After a defined interval, a few requests are allowed to test if the service has recovered.</p></li></ul><p>In short, a circuit breaker <strong>&#8220;breaks&#8221; the circuit</strong> when failures exceed a threshold and prevents your application from overloading a struggling service.</p><p></p><h2>Why Use a Circuit Breaker?</h2><p>Using a circuit breaker in your applications provides multiple benefits:</p><ol><li><p><strong>Improved Resilience</strong>: Prevents cascading failures when downstream services are unavailable or slow.</p></li><li><p><strong>Fail Fast</strong>: Instead of waiting for timeouts on failing services, requests fail immediately when the circuit is open.</p></li><li><p><strong>System Stability</strong>: Protects your resources (threads, memory) from being overwhelmed by repeated failures.</p></li><li><p><strong>Graceful Degradation</strong>: Gives your application time to recover or fallback to alternative flows.</p></li><li><p><strong>Operational Insights</strong>: Monitoring circuit states helps detect failing services early.</p></li></ol><p>In this article, we&#8217;ll explore how to implement the Cache-Aside pattern in <strong>.NET Core 9</strong> using <strong>HybridCache</strong>, which supports both in-memory and Redis caching.</p><p></p><h2>Setting Up a Circuit Breaker in ASP.NET Core</h2><p>First, add the required NuGet packages for caching:</p><pre><code><code>dotnet add package Microsoft.Extensions.Http.Resilience</code></code></pre><p>In ASP.NET Core, you can implement the Circuit Breaker pattern easily using <strong>Microsoft.Extensions.Http.Resilience</strong>. Here&#8217;s a sample configuration:</p><pre><code>// Add HttpClient with Circuit Breaker
builder.Services.AddHttpClient("ExternalApiClient")
    .AddResilienceHandler("circuit-breaker-pipeline", builder =&gt;
    {
        builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
        {
            FailureRatio = 0.1,       // 10% failure ratio triggers the circuit
            MinimumThroughput = 10,   // Minimum number of requests before evaluation
            SamplingDuration = TimeSpan.FromSeconds(30),
            BreakDuration = TimeSpan.FromSeconds(15) // Open circuit for 15 seconds
        });
    });</code></pre><h2>Using the Circuit Breaker in Controllers</h2><p>After configuring the <code>HttpClient</code>, you can use it in your controller:</p><pre><code>namespace CircuitBreakerApi.Controllers
{
    using Microsoft.AspNetCore.Mvc;
    using System.Net.Http;
    using System.Threading.Tasks;

    [ApiController]
    [Route("[controller]")]
    public class ExternalApiController : ControllerBase
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public ExternalApiController(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        [HttpGet("data")]
        public async Task&lt;IActionResult&gt; GetExternalData()
        {
            var client = _httpClientFactory.CreateClient("ExternalApiClient");

            try
            {
                var response = await client.GetAsync("https://jsonplaceholder.typicode.com/todos/1");

                if (response.IsSuccessStatusCode)
                {
                    var data = await response.Content.ReadAsStringAsync();
                    return Ok(data);
                }
                else
                {
                    return StatusCode((int)response.StatusCode, "External API failed");
                }
            }
            catch (Exception ex)
            {
                // Circuit breaker will throw if the circuit is open
                return StatusCode(503, $"Request failed: {ex.Message}");
            }
        }
    }
}</code></pre><h2>How It Works</h2><ol><li><p><strong>Monitoring</strong>: The circuit breaker monitors requests and tracks failures.</p></li><li><p><strong>Breaking</strong>: If failures exceed the threshold, the circuit opens, preventing further requests to the failing service.</p></li><li><p><strong>Recovery</strong>: After a break duration, the circuit enters half-open, allowing a few requests to check if the service has recovered.</p></li><li><p><strong>Closing</strong>: If the service responds successfully, the circuit closes, and normal traffic resumes.</p></li></ol><p></p><h2>Benefits Recap</h2><ul><li><p><strong>Better user experience</strong>: Avoid long waits and timeouts for end users.</p></li><li><p><strong>Resource protection</strong>: Prevent threads, memory, and CPU from being exhausted.</p></li><li><p><strong>Faster failure detection</strong>: Immediate awareness when downstream services fail.</p></li><li><p><strong>Resilient systems</strong>: Helps maintain overall application stability.</p></li></ul><p></p><p><strong>Engaging Thought:</strong><br>Have you ever noticed your app slowing down because an external API was failing? Imagine how a circuit breaker could have stopped that cascade&#8212;how would your system behave differently under failure?</p><p></p><div><hr></div><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/CircuitBreaker">https://sourcecode.kanaiyakatarmal.com/CircuitBreaker</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Implementing the Cache-Aside Pattern in .NET Core 9 with HybridCache]]></title><description><![CDATA[Boost .NET Core Performance with the Cache-Aside Pattern]]></description><link>https://newsletter.kanaiyakatarmal.com/p/implementing-the-cache-aside-pattern</link><guid isPermaLink="false">https://newsletter.kanaiyakatarmal.com/p/implementing-the-cache-aside-pattern</guid><dc:creator><![CDATA[Kanaiya Katarmal]]></dc:creator><pubDate>Tue, 23 Sep 2025 04:31:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Y1Ys!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Caching is one of the most effective strategies to improve the performance and scalability of your applications. But not all caching strategies are created equal. One of the most common patterns is the <strong>Cache-Aside (Lazy Loading) Pattern</strong>, which ensures your application only fetches data from the cache if available, otherwise loads it from the database and stores it in the cache for future requests.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Y1Ys!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png 424w, https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png 848w, https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png 1272w, https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png" width="1456" height="598" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:598,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:215570,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.kanaiyakatarmal.com/i/174108481?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png 424w, https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png 848w, https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png 1272w, https://substackcdn.com/image/fetch/$s_!Y1Ys!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a601dee-de71-4c52-bcc9-45b1c9469eef_1940x797.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>In this article, we&#8217;ll explore how to implement the Cache-Aside pattern in <strong>.NET Core 9</strong> using <strong>HybridCache</strong>, which supports both in-memory and Redis caching.</p><p></p><h2>Why Cache-Aside?</h2><p>The Cache-Aside pattern works like this:</p><ol><li><p>Your application tries to read data from the cache.</p></li><li><p>If the data exists, return it immediately (cache hit).</p></li><li><p>If the data doesn&#8217;t exist (cache miss), fetch it from the database.</p></li><li><p>Store the fetched data in the cache for subsequent requests.</p></li></ol><p>This approach is highly flexible because your cache doesn&#8217;t need to be kept in sync automatically with the database, it&#8217;s updated only when necessary.</p><h2>Setting Up the Project</h2><p>First, add the required NuGet packages for caching:</p><pre><code>dotnet add package Microsoft.Extensions.Caching.Hybrid
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis</code></pre><p>We'll use <code>HybridCache</code> for managing cache across memory and Redis seamlessly.</p><p>Creating the Product Entity</p><pre><code>public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string SKU { get; set; }
}</code></pre><p>This is a simple <code>Product</code> entity for demonstration purposes.</p><h2>Creating the Repository and Service Layer</h2><p>We&#8217;ll define a repository interface to interact with the database and a service layer for caching logic.</p><p><strong>IProductService.cs</strong></p><pre><code>using CacheStrategiesSample.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

public interface IProductService
{
    Task&lt;Product?&gt; GetByIdAsync(int id);
}</code></pre><p><strong>ProductService.cs</strong></p><pre><code>using CacheStrategiesSample.Data;
using CacheStrategiesSample.Repository;
using Microsoft.Extensions.Caching.Hybrid;
using System.Collections.Generic;
using System.Threading.Tasks;

public class ProductService : IProductService
{
    private readonly IRepository&lt;Product&gt; _productRepository;
    private readonly HybridCache _cache;

    public ProductService(IRepository&lt;Product&gt; productRepository, HybridCache cache)
    {
        _productRepository = productRepository;
        _cache = cache;
    }

    public async Task&lt;Product?&gt; GetByIdAsync(int id)
    {
        string cacheKey = $"Product_{id}";
        var product = await _cache.GetOrCreateAsync&lt;Product?&gt;(cacheKey, async _ =&gt;
        {
            // Fetch from repository if not in cache
            return await _productRepository.GetByIdAsync(id);
        });
        return product;
    }

  
}</code></pre><p>Notice how the <code>GetByIdAsync</code> method handles caching. It first checks the cache and falls back to the repository if needed. This is the essence of the <strong>Cache-Aside pattern</strong>.</p><p><strong>Creating the API Controller</strong></p><pre><code>using CacheStrategiesSample.Data;
using CacheStrategiesSample.Services;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductController(IProductService productService)
    {
        _productService = productService;
    }
   
    [HttpGet("{id}")]
    public async Task&lt;IActionResult&gt; GetById(int id)
    {
        var product = await _productService.GetByIdAsync(id);
        if (product == null) return NotFound();
        return Ok(product);
    }
   
}</code></pre><p>With this setup, every request to <code>GetById</code> first checks the cache, reducing database load and improving response time.</p><h2>Benefits of Using HybridCache</h2><ul><li><p><strong>Seamless multi-level caching:</strong> In-memory + Redis.</p></li><li><p><strong>Automatic key expiration:</strong> Avoid stale data.</p></li><li><p><strong>Simple API:</strong> <code>GetOrCreateAsync</code> makes cache-aside implementation almost trivial.</p></li><li><p><strong>Flexible storage:</strong> Switch or combine cache providers without changing business logic.</p></li></ul><h2>Conclusion</h2><p>Implementing the Cache-Aside pattern in .NET Core 9 is straightforward with <strong>HybridCache</strong>. By combining <strong>repository-based data access</strong> with caching, you can dramatically improve your application&#8217;s performance, reduce database load, and maintain flexibility in your caching strategy.</p><p>This pattern is especially useful for high-traffic APIs where database hits are expensive and response speed matters.</p><div><hr></div><p>&#127775; Have you ever experienced a performance boost just by adding caching to your application? What strategy worked best for you?</p><div><hr></div><h2><strong>&#128073; Full working code available at:</strong></h2><p>&#128279;<a href="https://sourcecode.kanaiyakatarmal.com/CacheStrategiesSample">https://sourcecode.kanaiyakatarmal.com/CacheStrategiesSample</a></p><div><hr></div><p>I hope you found this guide helpful and informative.</p><p>Thanks for reading!</p><p>If you enjoyed this article, feel free to <strong>share it</strong> and <strong><a href="https://www.linkedin.com/in/kanaiyakatarmal/">follow me</a></strong> for more practical, developer-friendly content like this.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.kanaiyakatarmal.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kanaiya&#8217;s Newsletter! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>