<?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[The .NET Saturday Newsletter]]></title><description><![CDATA[One practical .NET tip every Saturday morning — the kind of stuff I wish someone had told me earlier in my career. ASP.NET Core, EF Core, Azure, architecture, and honest advice from real projects. ]]></description><link>https://mwaseemzakir.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!ap0G!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce5adccf-8015-4f87-8f8f-cbce902141d0_1254x1254.png</url><title>The .NET Saturday Newsletter</title><link>https://mwaseemzakir.substack.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 20 Aug 2026 11:41:41 GMT</lastBuildDate><atom:link href="https://mwaseemzakir.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Muhammad Waseem]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[mwaseemzakir@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[mwaseemzakir@substack.com]]></itunes:email><itunes:name><![CDATA[Muhammad Waseem]]></itunes:name></itunes:owner><itunes:author><![CDATA[Muhammad Waseem]]></itunes:author><googleplay:owner><![CDATA[mwaseemzakir@substack.com]]></googleplay:owner><googleplay:email><![CDATA[mwaseemzakir@substack.com]]></googleplay:email><googleplay:author><![CDATA[Muhammad Waseem]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The ASP .NET Core Pipeline Order Nobody Explains Properly]]></title><description><![CDATA[Use, Run, Map, and why swapping two lines in Program.cs can break auth silently]]></description><link>https://mwaseemzakir.substack.com/p/the-asp-net-core-pipeline-order-nobody</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/the-asp-net-core-pipeline-order-nobody</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 15 Aug 2026 04:00:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1d6a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.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_!1d6a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1d6a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!1d6a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!1d6a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!1d6a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1d6a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64392,&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://mwaseemzakir.substack.com/i/211198750?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.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_!1d6a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!1d6a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!1d6a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!1d6a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29270b5f-f6d3-4ccf-bd60-013f8cbe7194_1200x600.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 aria-hidden="true" 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><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><figcaption class="image-caption">The ASP.NET Core Pipeline Order Nobody Explains Properly</figcaption></figure></div><p><em><strong><a href="https://mwaseemzakir.com/sponsorship">Sponsor this newsletter to reach 10,000+ readers</a> <a href="https://mwaseemzakir.substack.com/p/LINK">&#8594;</a></strong></em></p><p>A request coming into an ASP .NET Core app doesn&#8217;t go straight to your controller. It passes through a stack of components first, things like auth, CORS, rate limiting, and exception handling. That stack is the middleware pipeline.</p><p>Each middleware sits in line, does its job, then either hands off to the next one or stops the request right there. That&#8217;s really it. Nest a few of these together, and you&#8217;ve got a pipeline.</p><p>A typical API pipeline might look like this:</p><pre><code><code>Request
  &#8595;
Exception Handling
  &#8595;
HTTPS Redirection
  &#8595;
Routing
  &#8595;
CORS
  &#8595;
Authentication
  &#8595;
Authorization
  &#8595;
Endpoint
  &#8595;
Response</code></code></pre><p>The response travels back through in reverse order:</p><pre><code><code>Request
  &#8595;
Middleware A
  &#8595;
Middleware B
  &#8595;
Middleware C
  &#8595;
Endpoint
  &#8593;
Middleware C
  &#8593;
Middleware B
  &#8593;
Middleware A
  &#8593;
Response</code></code></pre><p>That reverse trip is why middleware works so well for request timing, logging, and headers- anything that needs to wrap the whole request.</p><h2>Types of Middleware: Built-in and Custom</h2><p>Every middleware in your pipeline falls into one of two buckets: the ones ASP.NET Core ships for you, and the ones you write yourself.</p><h3>Built-in middleware</h3><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/C8fKj/3/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/add16106-2b3a-4ac9-ba5f-f4ea41912eda_1220x2014.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e27f239-adef-4f77-ab24-7f709e12edac_1220x2014.png&quot;,&quot;height&quot;:1027,&quot;title&quot;:&quot;Built-in Middleware You Should Know&quot;,&quot;description&quot;:&quot;&quot;,&quot;belowTheFold&quot;:true}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/C8fKj/3/" width="730" height="1027" frameborder="0" scrolling="no" loading="lazy"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>ASP.NET Core ships middleware for most of the cross-cutting concerns you&#8217;ll actually need: exception handling, HTTPS redirection, static files, routing, CORS, authentication, authorization, rate limiting, response compression. I&#8217;ve covered several of these individually: <a href="https://mwaseemzakir.substack.com/p/built-in-middleware-in-aspnet-core">built-in middleware</a>, <a href="https://mwaseemzakir.substack.com/p/enable-cross-origin-requests-cors">CORS</a>, <a href="https://mwaseemzakir.substack.com/p/ep-29-rate-limiting-middleware-in">rate limiting</a>, <a href="https://mwaseemzakir.substack.com/p/ep-28-response-compression-in-aspnet">response compression</a>, and <a href="https://mwaseemzakir.substack.com/p/ep-12-how-to-implement-global-exception">global exception handling</a>. Reach for these first; don&#8217;t hand-roll something the framework already gives you.</p><h3>Custom middleware</h3><p>Once you need logic specific to your own app- a header check, a tenant lookup, something tied to your domain- you write it yourself. There are three ways to do that.</p><p><strong>1. Request delegate (inline)</strong></p><p>The simplest option. Written directly in Program.cs with <code>app.Use</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;64a89d3e-3cad-4712-a0ca-241de9c98a5f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.Use(async (context, next) =&gt;
{
    // logic before the next middleware

    await next(context);

    // logic after the next middleware
});</code></pre></div><p>Skip the <code>next</code> call if you don&#8217;t want the pipeline to continue.</p><p><strong>2. By convention</strong></p><p>A dedicated class. ASP.NET Core recognizes it through its constructor and an <code>Invoke</code> or <code>InvokeAsync</code> method; no interface required.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;dc1c2fb4-30a8-4b33-a8eb-5225020689bc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestTimingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // logic before the next middleware

        await _next(context);

        // logic after the next middleware
    }
}</code></pre></div><p>Registered in Program.cs:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;eda1da0f-aa84-45d6-8e81-0d14718db821&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.UseMiddleware&lt;RequestTimingMiddleware&gt;();</code></pre></div><p><strong>3. Using a factory (IMiddleware)</strong></p><p>Same idea, but the class implements <code>IMiddleware</code> and is activated through DI instead of by convention.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;5c7cc5ed-62bb-4828-8c6a-188874bdae2e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class RequestTimingMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        // logic before the next middleware

        await next(context);

        // logic after the next middleware
    }
}</code></pre></div><p>Register the class in DI, then wire it into the pipeline the same way as before:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;145a203a-c212-494e-a8f1-6179d50b811a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">builder.Services.AddTransient&lt;RequestTimingMiddleware&gt;();
app.UseMiddleware&lt;RequestTimingMiddleware&gt;</code></pre></div><p>Worth the extra step when your middleware needs scoped dependencies; factory-based activation plays nicer with the container than convention-based middleware does. For most day-to-day cases, though, convention-based is enough.</p><h2>The Most Important Part: Calling the Next Middleware</h2><p>Consider this small middleware:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;8d184684-4612-4769-9ebb-3517c54282ec&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.Use(async (context, next) =&gt;
{
    Console.WriteLine("Before");

    await next(context);

    Console.WriteLine("After");
});</code></pre></div><p>The important line is <code>await next(context);</code>. It passes control to the next component. When the rest of the pipeline finishes, execution comes back to the line right after it.</p><p>With two middleware components, the order looks like this:</p><pre><code><code>Middleware 1 - Before
Middleware 2 - Before

Endpoint Executes

Middleware 2 - After
Middleware 1 - After</code></code></pre><p>Think of middleware as nested layers, not a one-way list. That mental model makes the whole pipeline click.</p><h2>Use vs Run vs Map</h2><p>You&#8217;ll see these three constantly while configuring an app. They solve different problems.</p><h3>Use</h3><p><code>Use</code> adds middleware that can continue to the next component.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;0766b5d2-bc67-4dd7-9b6e-fd58401d18fa&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.Use(async (context, next) =&gt;
{
    // Before downstream middleware

    await next(context);

    // After downstream middleware
});</code></pre></div><p>Typical examples: request logging, correlation IDs, request timing, authentication, authorization, CORS, rate limiting, security headers. Anything where you want to do something around the request and then let it continue.</p><h3>Run</h3><p><code>Run</code> adds terminal middleware. There&#8217;s no <code>next</code> delegate because processing ends here.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;c4ad9e18-8a7e-4919-b69f-b07eb1df22a5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.Run(async context =&gt;
{
    await context.Response.WriteAsync("Request handled.");
});</code></pre></div><p>Useful for a simple terminal response, maintenance mode, a fallback handler, or the end of a custom pipeline branch. Anything registered after it won&#8217;t run for requests that reach it, so use it deliberately.</p><h3>Map</h3><p><code>Map</code> creates a separate pipeline based on the request path.</p><pre><code><code>/api/*
    &#8594; API pipeline
    &#8594; API endpoints

/admin/*
    &#8594; Admin-specific middleware
    &#8594; Admin endpoints</code></code></pre><p>Handy when one part of your app needs different processing: admin routes, API vs non-API, legacy sections, tenant-specific branches. One gotcha: <code>Map</code> strips the matched segment off <code>Request.Path</code> and moves it into <code>Request.PathBase</code>, so code inside the branch reading <code>Request.Path</code> sees it without that prefix.</p><h2>Conditional Middleware: UseWhen vs MapWhen vs Map</h2><p><code>Map</code> only branches on path, and once a request goes down that branch, it never comes back to the main pipeline. Sometimes that&#8217;s too blunt. You want a middleware to apply only under some condition, without splitting your whole app into separate pipelines.</p><p>That&#8217;s what <code>UseWhen</code> and <code>MapWhen</code> are for, and they&#8217;re not interchangeable with each other either.</p><p><strong>UseWhen</strong>: branches on any condition, and rejoins the main pipeline afterward as long as nothing inside the branch short-circuits.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;b3f43587-c8cd-43db-8d00-2d1f7093235f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.UseWhen(
    context =&gt; context.Request.Path.StartsWithSegments("/api/user"),
    branch =&gt; branch.UseMiddleware&lt;YourMiddlewareName&gt;()
);

app.UseAuthorization();
app.MapControllers();</code></pre></div><p>Requests to <code>/api/user/*</code> pick up <code>YourMiddlewareName</code> on the way through, then continue on to authorization and the controllers exactly like every other request. Nothing else about the pipeline changes for them.</p><p><strong>MapWhen</strong> branches on any condition too, but behaves like <code>Map</code>, the branch does not rejoin.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;2770f244-75b6-47ab-82e3-76e8309e39e3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.MapWhen(
    context =&gt; context.Request.Headers.ContainsKey("X-Beta-User"),
    betaApp =&gt;
    {
        betaApp.UseMiddleware&lt;BetaFeatureMiddleware&gt;();

        betaApp.Run(async context =&gt;
        {
            await context.Response.WriteAsync("Beta experience");
        });
    });</code></pre></div><p>Two questions settle which one you need. Does the branch need to rejoin the main pipeline, or is it a dead end. And are you branching on the path, or on something else entirely: a header, a query string, the user&#8217;s claims. <code>Map</code> and <code>MapWhen</code> are dead ends and only <code>Map</code> is path-only. <code>UseWhen</code> rejoins and can check anything.</p><h2>What Should Middleware Handle?</h2><p>Middleware is built for cross-cutting concerns tied to the HTTP request itself. Say you want to time every request. Adding that logic to every controller duplicates infrastructure code across the app. Middleware gives you one place to do it once.</p><p>Good candidates: global exception handling, request/response logging, correlation IDs, authentication, authorization, CORS, rate limiting, request timing, security headers, response compression.</p><p>Ask yourself whether the concern applies to the HTTP request as a whole. If it does, middleware&#8217;s usually the right starting point. ASP.NET Core already ships middleware for most of these (see the built-in middleware section above), so don&#8217;t rebuild what&#8217;s already there.</p><h2>Middleware Order Matters</h2><p>Middleware runs in the order you register it.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;42424e4d-ea62-4869-a6fd-22545ea874bc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.UseAuthentication();
app.UseAuthorization();</code></pre></div><p>Authentication figures out who the user is. Authorization decides whether that user can access what they&#8217;re asking for. Flip those two, and you get broken behavior that looks like a completely unrelated bug.</p><p>A simplified pipeline:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;62501c1b-fbdb-4be7-abb6-ad8949157d39&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthentication();
app.UseAuthorization();

app.UseRateLimiter();

app.MapControllers();</code></pre></div><p>Don&#8217;t copy this into every project blindly. The right order depends on what middleware you&#8217;re actually using. What matters is the relationships: exception handling needs to sit early enough to catch what&#8217;s downstream, authentication comes before authorization, CORS has to run at the right point for the endpoints it covers, and anything that short-circuits blocks everything registered after it.</p><p>Order isn&#8217;t a formality here. Get auth and authorization backwards, and you&#8217;ll spend an hour debugging something that was never really a bug.</p><h2>What Is Short-Circuiting?</h2><p>A middleware doesn&#8217;t have to call the next component. Say your app requires a specific header:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;29f8eafc-890c-4cb8-8505-7325d0f7c2f3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.Use(async (context, next) =&gt;
{
    if (!context.Request.Headers.ContainsKey("X-API-Key"))
    {
        context.Response.StatusCode =
            StatusCodes.Status401Unauthorized;

        await context.Response.WriteAsync(
            "API key is missing.");

        return;
    }

    await next(context);
});</code></pre></div><p>Missing header, response goes out immediately, nothing later in the pipeline runs. That&#8217;s short-circuiting. Useful for rate-limit rejection, maintenance mode, invalid requests, cached responses, security checks.</p><p>Watch out when writing custom middleware, though. Forgetting to call <code>next</code> when you meant to continue makes everything after it look broken for no obvious reason.</p><h2>Middleware vs Filters: Which One Should You Use?</h2><p>Both can wrap logic around something else, so they get confused a lot. The difference is where that logic runs.</p><p>Middleware only sees the HttpContext; it has no idea which controller or action got picked. Filters run after that decision&#8217;s already been made, so they&#8217;re the right call once you need the action&#8217;s arguments or its result.</p><p>Correlation IDs, request logging, global exception handling: middleware. Inspecting an action&#8217;s arguments or modifying its result: filter. A client IP restriction could go either way; I showed both approaches in my piece on <a href="https://mwaseemzakir.substack.com/p/ep-34-client-ip-safelist-for-aspnet">client IP safelisting in ASP.NET Core</a>.</p><h2>Summary</h2><ul><li><p>Order isn&#8217;t cosmetic; get auth and authorization backwards, and the bug won&#8217;t look like an order problem</p></li><li><p>Short-circuit when finishing the pipeline would be wasted work</p></li><li><p>Middleware for anything that only needs HttpContext; filters once you need the action itself</p></li></ul><h3><strong>Whenever you&#8217;re ready, there are 2 ways I can help you:</strong></h3><ol><li><p>I work with founders and growing businesses to design, build, and improve reliable web apps. <em><strong><a href="https://mwaseemzakir.com/work-with-me">Let&#8217;s talk &#8594;</a></strong></em></p></li><li><p>Promote yourself to 10,000+ subscribers by <em><strong><a href="https://mwaseemzakir.com/sponsorship">sponsoring this newsletter</a> <a href="https://mwaseemzakir.substack.com/p/LINK">&#8594;</a></strong></em></p></li><li><p>Boost your .NET skills by subscribing to my <em><strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ">YouTube Channel</a> <a href="https://mwaseemzakir.substack.com/p/LINK">&#8594;</a></strong></em></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Migrating From MinIO to Cloudflare R2 (And Saving Myself a VPS Upgrade)]]></title><description><![CDATA[How a bandwidth warning email led me to rethink where PakExam stores its files.]]></description><link>https://mwaseemzakir.substack.com/p/migrating-from-minio-to-cloudflare</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/migrating-from-minio-to-cloudflare</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 08 Aug 2026 04:00:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!DTsj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.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_!DTsj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DTsj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!DTsj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!DTsj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!DTsj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DTsj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:70178,&quot;alt&quot;:&quot;Migrating From MinIO to Cloudflare R2 (And Saving Myself a VPS Upgrade)&quot;,&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://mwaseemzakir.substack.com/i/210030945?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Migrating From MinIO to Cloudflare R2 (And Saving Myself a VPS Upgrade)" title="Migrating From MinIO to Cloudflare R2 (And Saving Myself a VPS Upgrade)" srcset="https://substackcdn.com/image/fetch/$s_!DTsj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!DTsj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!DTsj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!DTsj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89999aa9-2a75-4f32-8afa-725794d9392e_1200x600.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 aria-hidden="true" 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><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><figcaption class="image-caption">Migrating From MinIO to Cloudflare R2 (And Saving Myself a VPS Upgrade)</figcaption></figure></div><p><em><strong><a href="https://mwaseemzakir.com/sponsorship">Sponsor this newsletter to reach 10,000+ readers</a> <a href="LINK">&#8594;</a></strong></em></p><p>Most developers create a new Web API and immediately start writing controllers, entities, and endpoints. I got an email from Hostinger last month. 97% of my monthly bandwidth used; 5 days left in the billing cycle. Upgrade or get throttled.</p><p>For my PakExam SaaS, I&#8217;m running a Hostinger KVM2:</p><ul><li><p>2 vCPU cores</p></li><li><p>8 GB RAM</p></li><li><p>100 GB NVMe disk space</p></li><li><p>8 TB bandwidth</p></li></ul><p>PakExam is built for students preparing for exams, and every time they download a past paper or a resource, that&#8217;s bandwidth. Last month I nearly ran out of the 8 TB, and the &#8220;solution&#8221; Hostinger pushes on you is simple: upgrade to KVM4.</p><p>I&#8217;d already been thinking about moving off Hostinger after 6 months there. Nothing wrong with them, but for a SaaS that hasn&#8217;t even turned subscriptions on yet, I don&#8217;t want to lock myself into a bigger, costlier plan just to fix one thing. KVM2 cost me $101.88 the first year and renews at $200 for the second. I didn&#8217;t check that going in. Lesson learned: always check the renewal price, not just the first-year offer, because by the time it kicks in you&#8217;re already dependent on the setup and switching feels like a bigger job than it should.</p><p>So before paying for a bigger VPS, I wanted to know what was actually eating the bandwidth.</p><h3>What was actually eating my bandwidth?</h3><p>I was running MinIO, the open-source S3-compatible storage server, deployed directly on my VPS. Every past paper PDF, every image, every audio (my dictionary feature provides English-to-Urdu translation) file the app served was going through that same box and the same bandwidth allowance as everything else.</p><p>Storage traffic was the biggest chunk of it. Students downloading papers all day, every day, all counted against the same 8 TB as my API and my site.</p><p>Once I saw that, the fix wasn&#8217;t &#8220;buy a bigger server.&#8221; It was &#8220;stop making my server serve files.&#8221;</p><h3>Why Cloudflare R2</h3><p>I&#8217;d heard about R2 before but never had a real reason to move. Cloudflare&#8217;s free tier for R2 is generous:</p><ul><li><p>10 GB-month of standard storage</p></li><li><p>1 million Class A operations (writes, lists)</p></li><li><p>10 million Class B operations (reads)</p></li><li><p>Completely free egress, no matter how much data goes out</p></li></ul><p>That last point is the whole reason this made sense for me. Egress is exactly what was costing me bandwidth on Hostinger, and R2 doesn&#8217;t charge for it at all.</p><p>The other advantage: MinIO and R2 are both S3-compatible. My whole setup was already speaking that protocol, so this wasn&#8217;t a rewrite; it was a swap.</p><h3>How I moved the data</h3><p>Here&#8217;s how I moved everything over:</p><ol><li><p>Installed rclone on the server. Since both MinIO and R2 speak S3, this one tool could talk to both.</p></li><li><p>Grabbed my R2 credentials from the Cloudflare dashboard: an Access Key ID and Secret Access Key scoped to Object Read &amp; Write.</p></li><li><p>Created matching buckets on R2 first, same names as my MinIO buckets, so nothing in my app code would need to change beyond the endpoint.</p></li><li><p>Set up two rclone remotes, one pointing at MinIO (source) and one at R2 (destination), using the same S3 provider type for both.</p></li><li><p>Ran a dry run on each bucket before touching anything real, just to see file counts and sizes and catch surprises early.</p></li><li><p>Copied bucket by bucket with rclone, checking progress live as it transferred.</p></li><li><p>Verified everything with <code>rclone check</code> on each bucket to confirm the file counts and checksums matched between MinIO and R2.</p></li></ol><p>Once every bucket checked out clean, I swapped my app&#8217;s storage config over to R2 and kept MinIO around for a few days as a safety net before shutting it down for good.</p><h3>What changed in the code</h3><p>The config side got a new section for R2 alongside the existing MinIO one, with a switch to pick which provider is active:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;31fa0fe8-7270-4145-96e6-e78dde918754&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">"MinIOStorage": {
  "Endpoint": "yourendpoint.yourdomain.com",
  "UseSSL": true,
  "DefaultBucket": "defaultBucketName"
},
"CloudflareR2Storage": {
  "AccountId": "accountId",
  "Region": "auto/regionName",
  "DefaultBucket": "defaultBucketName"
},
"Storage": {
  "BaseUrl": "baseURL",
  "ActiveProvider": "CloudflareR2"
}</code></pre></div><p>Under the hood, I built a new storage service for R2 and wired it into the same storage abstraction the rest of the app already uses. Since every part of the codebase that reads or writes a file goes through that interface, not a concrete MinIO class, none of those call sites needed to change. Only the DI registration and the new service itself did.</p><p>That new service and the DI setup around it deserves its own walkthrough, so I&#8217;ll cover the actual implementation in a separate article.</p><p>This isn&#8217;t the first infrastructure decision I&#8217;ve made for PakExam as a solo dev. I wrote a bit ago about why I went with a monorepo for the whole project; similar reasoning there: fewer moving parts, less to manage alone.</p><h2><strong>Summary</strong></h2><ul><li><p>MinIO on my own VPS meant every file download counted against my server&#8217;s bandwidth allowance.</p></li><li><p>R2&#8217;s free egress moved that cost to $0, without giving up S3 compatibility.</p></li><li><p>rclone made the actual data migration a copy-and-verify job.</p></li><li><p>A new storage service implementing the existing abstraction meant switching providers was a config change plus one new service, not a rewrite of the app.</p></li></ul><h3><strong>Whenever you&#8217;re ready, there are 2 ways I can help you:</strong></h3><ol><li><p>I work with founders and growing businesses to design, build, and improve reliable web apps. <em><strong><a href="https://mwaseemzakir.com/work-with-me">Let's talk &#8594;</a></strong></em></p></li><li><p><span>Promote yourself to 10,000+ subscribers by </span><em><strong><a href="https://mwaseemzakir.com/sponsorship"><span>sponsoring this newsletter</span></a><span> </span><a href="LINK">&#8594;</a></strong></em></p></li><li><p><span>Boost your .NET skills by subscribing to my </span><em><strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ">YouTube Channel</a> <a href="LINK">&#8594;</a></strong></em></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Before You Start a .NET Web API, Add These 3 Files]]></title><description><![CDATA[Set up consistent code style, centralized package versions, and clear AI instructions before writing your first endpoint.]]></description><link>https://mwaseemzakir.substack.com/p/before-you-start-a-net-web-api-add</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/before-you-start-a-net-web-api-add</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 01 Aug 2026 06:53:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!DpS1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.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_!DpS1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DpS1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!DpS1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!DpS1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!DpS1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DpS1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:61466,&quot;alt&quot;:&quot;Before You Start a .NET Web API, Add These 3 Files&quot;,&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://mwaseemzakir.substack.com/i/209347607?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Before You Start a .NET Web API, Add These 3 Files" title="Before You Start a .NET Web API, Add These 3 Files" srcset="https://substackcdn.com/image/fetch/$s_!DpS1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!DpS1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!DpS1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!DpS1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f8b1f98-d87f-4448-94ef-656d32e273d0_1200x600.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 aria-hidden="true" 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><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><figcaption class="image-caption">Before You Start a .NET Web API, Add These 3 Files</figcaption></figure></div><p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><p>Most developers create a new Web API and immediately start writing controllers, entities, and endpoints.</p><p>That works until the project grows.</p><p>Then formatting becomes inconsistent, NuGet versions start drifting, and your AI coding assistant keeps making decisions that do not match your architecture.</p><p>Before writing the first endpoint, I recommend setting up these three things:</p><ol><li><p><code>.editorconfig</code></p></li><li><p>Central NuGet Package Management</p></li><li><p><code>CLAUDE.md</code></p></li></ol><p>They take a few minutes to configure and save hours of cleanup later.</p><h2>1. Add an <code>.editorconfig</code> File</h2><p>The <code>.editorconfig</code> file defines how code should look across the entire solution.</p><p>It can control:</p><ul><li><p>Indentation and spacing</p></li><li><p>File-scoped namespaces</p></li><li><p>Brace placement</p></li><li><p>Use of <code>var</code></p></li><li><p>Naming conventions</p></li><li><p>Unused imports</p></li><li><p>Code-analysis severity</p></li><li><p>Special rules for migrations and tests</p></li></ul><p>For example:</p><pre><code><code>root = true

[*.cs]

indent_size = 4
indent_style = space

csharp_style_namespace_declarations = file_scoped
dotnet_style_readonly_field = true
dotnet_diagnostic.IDE0005.severity = warning
</code></code></pre><p>Without an<code>.editorconfig</code>, every developer&#8217;s IDE can format code differently.</p><p>One person uses <code>var</code>. Another writes explicit types. One removes braces. Another adds them back.</p><p>Soon, your pull requests contain more formatting changes than actual business logic.</p><p>A shared <code>.editorconfig</code> gives the whole team one coding standard. Your existing configuration can also enforce interface naming, PascalCase members, analyzer rules, migration exclusions, and test-specific exceptions.</p><p>The goal is simple:</p><blockquote><p>Stop debating formatting during code reviews.</p></blockquote><p>Let the configuration handle it.</p><h2>2. Enable Central NuGet Package Management</h2><p>In a solution with multiple projects, package versions can quickly become inconsistent.</p><p>Your API project might use one version of Entity Framework Core while your Persistence project uses another.</p><p>That is how strange restore errors and dependency conflicts begin.</p><p>Create a <code>Directory.Packages.props</code> file at the solution root:</p><pre><code><code>&lt;Project&gt;
  &lt;PropertyGroup&gt;
    &lt;ManagePackageVersionsCentrally&gt;true&lt;/ManagePackageVersionsCentrally&gt;

    &lt;CentralPackageTransitivePinningEnabled&gt;
      true
    &lt;/CentralPackageTransitivePinningEnabled&gt;
  &lt;/PropertyGroup&gt;

  &lt;ItemGroup&gt;
    &lt;PackageVersion
      Include="Microsoft.EntityFrameworkCore"
      Version="9.0.4" /&gt;

    &lt;PackageVersion
      Include="Npgsql.EntityFrameworkCore.PostgreSQL"
      Version="9.0.4" /&gt;

    &lt;PackageVersion
      Include="Serilog.AspNetCore"
      Version="9.0.0" /&gt;

    &lt;PackageVersion
      Include="OpenTelemetry.Extensions.Hosting"
      Version="1.15.3" /&gt;
  &lt;/ItemGroup&gt;
&lt;/Project&gt;
</code></code></pre><p>Your individual project files can then reference packages without specifying versions:</p><pre><code><code>&lt;ItemGroup&gt;
  &lt;PackageReference Include="Microsoft.EntityFrameworkCore" /&gt;
  &lt;PackageReference Include="Serilog.AspNetCore" /&gt;
&lt;/ItemGroup&gt;
</code></code></pre><p>Now every project gets its package versions from one place.</p><p>This gives you:</p><ul><li><p>One place to update packages</p></li><li><p>Consistent versions across projects</p></li><li><p>Easier security upgrades</p></li><li><p>Fewer dependency conflicts</p></li><li><p>Cleaner <code>.csproj</code> files</p></li></ul><p><code>CentralPackageTransitivePinningEnabled</code> also lets NuGet promote and pin a transitive dependency when a centrally defined version needs to be enforced.</p><p>You can combine this with a <code>Directory.Build.props</code> file for solution-wide settings:</p><pre><code><code>&lt;Project&gt;
  &lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net9.0&lt;/TargetFramework&gt;

    &lt;Nullable&gt;enable&lt;/Nullable&gt;

    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;

    &lt;AnalysisLevel&gt;latest&lt;/AnalysisLevel&gt;

    &lt;TreatWarningsAsErrors&gt;true&lt;/TreatWarningsAsErrors&gt;

    &lt;EnforceCodeStyleInBuild&gt;true&lt;/EnforceCodeStyleInBuild&gt;
  &lt;/PropertyGroup&gt;
&lt;/Project&gt;
</code></code></pre><p>A good rule is:</p><blockquote><p>Package versions belong in <code>Directory.Packages.props</code>. Shared build rules belong in <code>Directory.Build.props</code>.</p></blockquote><p>Also, do not copy a hundred packages into a new project just because you might need them someday.</p><p>Add packages when the project actually needs them. NuGet packages are dependencies, not Pok&#233;mon.</p><h2>3. Create a <code>CLAUDE.md</code> File</h2><p>If you use Claude Code or another AI coding assistant, create a <code>CLAUDE.md</code> file at the repository root.</p><p>This file acts as the project&#8217;s instruction manual.</p><p>It should explain:</p><ul><li><p>What the application does</p></li><li><p>Which technologies it uses</p></li><li><p>How the solution is structured</p></li><li><p>Where new features should go</p></li><li><p>Which commands should be used</p></li><li><p>How migrations should be created</p></li><li><p>How tests should be organized</p></li><li><p>Which architectural rules must never be broken</p></li><li><p>Which actions require explicit approval</p></li></ul><p>For example:</p><pre><code><code># Project Guidelines

This project is a .NET 9 Web API using Clean Architecture,
EF Core, PostgreSQL, Wolverine, and OpenTelemetry.

## Architecture

- Domain contains entities and domain events.
- Application contains business logic and abstractions.
- Persistence contains EF Core and service implementations.
- Infrastructure contains external integrations.
- API contains controllers and dependency injection.

## Important Rules

- Never use DbContext directly in the Application layer.
- Never create EF Core migrations manually.
- Every new feature must include tests.
- Never run git push unless explicitly requested.
- Use fluent LINQ syntax instead of query syntax.
</code></code></pre><p>Without project instructions, the AI has to guess.</p><p>It may place database code inside the Application layer, create migrations manually, skip tests, introduce a different folder structure, or push code when you only asked it to make a local change.</p><p>A detailed <code>CLAUDE.md</code> can document commands, architecture, feature workflows, security rules, testing expectations, deployment constraints, and even instructions such as never running <code>git push</code> without permission.</p><p>The AI does not know your unwritten rules.</p><p>Write them down once, and you will not need to repeat them in every prompt.</p><h2>My Recommended Setup Order</h2><p>When starting a new Web API, I now follow this order:</p><ol><li><p>Create the solution and projects.</p></li><li><p>Add <code>.editorconfig</code>.</p></li><li><p>Add <code>Directory.Build.props</code>.</p></li><li><p>Add <code>Directory.Packages.props</code>.</p></li><li><p>Add <code>CLAUDE.md</code>.</p></li><li><p>Configure analyzers and warnings.</p></li><li><p>Run <code>dotnet build</code>.</p></li><li><p>Only then start building features.</p></li></ol><p>These files are not exciting.</p><p>They will not appear in your product demo, and your client will never ask about them.</p><p>But they create the foundation for consistent code, predictable builds, controlled dependencies, and safer AI-assisted development.</p><p>A clean project does not begin with the first controller.</p><p>It begins with the rules that every controller must follow.</p><p></p><h3><strong>Whenever you&#8217;re ready, there are 2 ways I can help you:</strong></h3><ol><li><p><span>Boost your .NET skills by subscribing to my </span><strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ">YouTube Channel</a></strong></p></li><li><p>Promote yourself to 10,000+ subscribers<span> by sponsoring this newsletter</span></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Cleaning Up Ghost Users After OTP Registration in .NET]]></title><description><![CDATA[How to model pending signups, index them cheaply, and auto-delete unverified users with a Quartz background job.]]></description><link>https://mwaseemzakir.substack.com/p/cleaning-up-ghost-users-in-dotnet</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/cleaning-up-ghost-users-in-dotnet</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 25 Jul 2026 04:01:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!b6b1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!b6b1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b6b1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!b6b1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!b6b1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!b6b1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b6b1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63759,&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://mwaseemzakir.substack.com/i/206566218?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.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_!b6b1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!b6b1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!b6b1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!b6b1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f6080ff-e90c-4d3f-bd11-2f5449a339fe_1200x600.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 aria-hidden="true" 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><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>Someone starts signing up on your app. They enter their email, hit &#8220;Register,&#8221; and then... never open the OTP email.</p><p>They&#8217;re gone. But their row isn&#8217;t.</p><p>Six months later your <code>users</code> table has 40,000 rows and only 22,000 are real. Your signup conversion metric is lying to you. And worse, that abandoned email is sitting in a unique column, so the actual person can never register with it again.</p><p>Here&#8217;s how to stop that from happening.</p><div><hr></div><h2>The Naive Way</h2><p>Most registration flows look like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;d73faf76-fd3d-4a6b-b64b-4b36b05da314&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.MapPost("/register", async (RegisterRequest request, AppDbContext db) =&gt;
{
    var user = new User
    {
        Id = Guid.NewGuid(),
        Email = request.Email,
        PasswordHash = Hasher.Hash(request.Password),
        CreatedAtUtc = DateTime.UtcNow
    };

    db.Users.Add(user);

    await db.SaveChangesAsync();

    await otpService.SendOtpAsync(user.Email);

    return Results.Ok("Check your email for the OTP.");
});
</code></pre></div><p>This works fine on day one. The problem shows up over time:</p><ul><li><p>The row is indistinguishable from a real, verified user. Nothing marks it as incomplete.</p></li><li><p><code>Email</code> is unique, so if this person abandons the flow, nobody, including them, can ever register with that email again.</p></li><li><p>Your table grows forever with rows that never convert.</p></li><li><p>Every dashboard counting &#8220;total users&#8221; or &#8220;signups today&#8221; is quietly wrong.</p></li></ul><p>There&#8217;s no cleanup mechanism here at all. That&#8217;s the gap we need to close.</p><div><hr></div><h2>Why This Happens</h2><p>A registration and an OTP verification are really two separate events, but this code treats them as one. The row gets created as if the user already exists, before they&#8217;ve proven they own that email.</p><p>The fix is to stop treating &#8220;registered&#8221; and &#8220;verified&#8221; as the same thing. A user isn&#8217;t a user until they&#8217;ve confirmed the OTP. Until then, they&#8217;re a <strong>pending</strong> row with an expiry date.</p><p>Once you model it that way, cleanup becomes a scheduled job instead of a manual database chore.</p><p>There are two solid ways to structure this:</p><p><strong>Option 1: one table, a status column.</strong> The user row is inserted as <code>Pending</code>, flipped to <code>Active</code> on OTP verification, and hard-deleted by a background job if it never gets there. One table, no migration logic between states.</p><p><strong>Option 2: two tables.</strong> Unverified signups live in <code>pending_users</code>. On successful OTP verification, the row moves into <code>users</code>. Your main table never has a status flag or a ghost row in it, every query against <code>users</code> is guaranteed clean.</p><p>For a fast-moving SaaS, option 1 is the pragmatic default. It&#8217;s what I run in production. Option 2 earns its keep once <code>users</code> gets queried constantly across many services and you don&#8217;t want every one of them filtering by status. I&#8217;ll implement option 1 in full below, since that&#8217;s what most teams need first.</p><div><hr></div><h2>Step 1: Add a Status to the User</h2><p>Supposing we already have the <code>User</code> entity:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;240b441d-a8dd-4951-8f92-f333e280bc3a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class User
{
    public Guid Id { get; set; }
    public string Email { get; set; } = default!;
    public string PasswordHash { get; set; } = default!;
    public DateTime CreatedAtUtc { get; set; }
    public string? OtpCode { get; set; }
    public DateTime? OtpExpiresAtUtc { get; set; }
}
</code></pre></div><p>Introduce a user status enum:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;5e6d88d1-b359-4b39-8c8d-a7eac46f45d0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public enum UserStatus
{
    Pending,
    Active
}</code></pre></div><p>Update the user entity now:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;dca290eb-3467-4122-b9da-8361bee7172f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class User
{
    public Guid Id { get; set; }
    public string Email { get; set; } = default!;
    public string PasswordHash { get; set; } = default!;
    public UserStatus Status { get; set; } = UserStatus.Pending;
    public DateTime CreatedAtUtc { get; set; }
    public string? OtpCode { get; set; }
    public DateTime? OtpExpiresAtUtc { get; set; }
}</code></pre></div><p>Every user starts life as <code>Pending</code>. Nothing is &#8220;active&#8221; until it&#8217;s proven.</p><p>If you&#8217;re validating <code>RegisterRequest</code> with data annotations, <a href="https://mwaseemzakir.substack.com/p/data-annotations-in-c">Data Annotations in C#</a> covers the attributes worth reaching for on a request DTO like this or if you are fan of FluentValidation then check <a href="https://mwaseemzakir.substack.com/p/ep-58-fluent-validation-in-net">FluentValidation in .NET</a></p><div><hr></div><h2>Step 2: Insert as Pending, Generate the OTP</h2><p>Two things need to change from the naive version above: the insert has to carry OTP data, and the email has to carry the OTP itself. Start with generating the value:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;063a1170-2ebf-4b22-8cb0-a623a4771c29&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">var otp = otpService.GenerateOtp();</code></pre></div><p>That value now needs to land on the user row, along with an explicit <code>Pending</code> status and an expiry:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;25424642-0296-414b-81da-d1d58595a3df&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">Status = UserStatus.Pending,
CreatedAtUtc = DateTime.UtcNow,
OtpCode = otp,
OtpExpiresAtUtc = DateTime.UtcNow.AddMinutes(10)</code></pre></div><p>Full endpoint with both pieces wired in:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;f39b331c-304b-4cc4-adf8-8543aef87ee8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.MapPost("/register", async (RegisterRequest request, AppDbContext db, IOtpService otpService) =&gt;
{
    var otp = otpService.GenerateOtp();

    var user = new User
    {
        Id = Guid.NewGuid(),
        Email = request.Email,
        PasswordHash = Hasher.Hash(request.Password),
        Status = UserStatus.Pending,
        CreatedAtUtc = DateTime.UtcNow,
        OtpCode = otp,
        OtpExpiresAtUtc = DateTime.UtcNow.AddMinutes(10)
    };

    db.Users.Add(user);
    await db.SaveChangesAsync();

    await otpService.SendOtpAsync(user.Email, otp);

    return Results.Ok("Check your email for the OTP.");
});
</code></pre></div><p>Same insert as before, but now the row is honest about what it is. Nothing downstream should treat this user as real until <code>Status</code> flips.</p><div><hr></div><h2>Step 3: Flip to Active on Verification</h2><p>First, find the pending user by email and make sure one exists:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;8322b55b-a503-440a-8819-249ab810e2ad&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">var user = await db.Users
    .FirstOrDefaultAsync(u =&gt; u.Email == request.Email &amp;&amp; u.Status == UserStatus.Pending);

if (user is null)
    return Results.NotFound("No pending registration found for this email.");
</code></pre></div><p>Then check the OTP itself before touching anything:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;addc7ad6-864a-44a6-b863-52bc47754fec&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">if (user.OtpCode != request.OtpCode || user.OtpExpiresAtUtc &lt; DateTime.UtcNow)
    return Results.BadRequest("Invalid or expired OTP.");</code></pre></div><p>Once both checks pass, flip the status and clear the OTP fields so they can&#8217;t be reused:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;f59eac68-a34d-4410-84ec-2db72b734250&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">user.Status = UserStatus.Active;
user.OtpCode = null;
user.OtpExpiresAtUtc = null;

await db.SaveChangesAsync();

return Results.Ok("Account verified.");</code></pre></div><p>Full endpoint:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;3054a309-81f1-4809-a760-f1ec83f5a5a7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">app.MapPost("/verify-otp", async (VerifyOtpRequest request, AppDbContext db) =&gt;
{
    var user = await db.Users
        .FirstOrDefaultAsync(u =&gt; u.Email == request.Email &amp;&amp; u.Status == UserStatus.Pending);

    if (user is null)
        return Results.NotFound("No pending registration found for this email.");

    if (user.OtpCode != request.OtpCode || user.OtpExpiresAtUtc &lt; DateTime.UtcNow)
        return Results.BadRequest("Invalid or expired OTP.");

    user.Status = UserStatus.Active;
    user.OtpCode = null;
    user.OtpExpiresAtUtc = null;

    await db.SaveChangesAsync();

    return Results.Ok("Account verified.");
});</code></pre></div><p>This is the only place a user ever becomes <code>Active</code>. If you&#8217;re routing this through a mediator instead of a minimal API endpoint, the handler slots in the same way I described in <a href="https://mwaseemzakir.substack.com/p/wolverine-as-mediator-in-net">Wolverine as Mediator in .NET</a>, same logic, just invoked through <code>IMessageBus</code> instead of <code>MapPost</code>.</p><div><hr></div><h2>Step 4: Index the Pending Rows</h2><p>Your cleanup job is going to scan for <code>Pending</code> rows on every run. Without an index, that&#8217;s a full table scan as your <code>users</code> table grows.</p><p>A <strong>partial index</strong>, one that only covers <code>Pending</code> rows, keeps that scan cheap without bloating the index for your (much larger) set of active users:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;64c88afb-a290-45a6-8096-b42769d3a987&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity&lt;User&gt;()
        .HasIndex(u =&gt; u.CreatedAtUtc)
        .HasFilter("\"Status\" = 0") // 0 = Pending
        .HasDatabaseName("IX_Users_Pending_CreatedAt");
}</code></pre></div><p><code>Status = 0</code> matches <code>UserStatus.Pending</code>, since EF Core stores enums as integers by default. Active users, the vast majority of your table over time, never touch this index.</p><div><hr></div><h2>Step 5: Sweep the Ghosts with Quartz</h2><p>I already covered the basics of wiring Quartz.NET into a .NET app back in <a href="https://mwaseemzakir.substack.com/p/episode-3-schedule-jobs-with-quartznet">Episode 3: Schedule Jobs with Quartz.NET</a>. This is that same scheduler pointed at a much narrower job.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;ba693302-8fcf-43e9-86be-855e8a3ca706&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class GhostUserCleanupJob : IJob
{
    private readonly AppDbContext _db;
    private readonly ILogger&lt;GhostUserCleanupJob&gt; _logger;

    public GhostUserCleanupJob(AppDbContext db, ILogger&lt;GhostUserCleanupJob&gt; logger)
    {
        _db = db;
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        var cutoff = DateTime.UtcNow.AddMinutes(-30);

        var ghosts = await _db.Users
            .Where(u =&gt; u.Status == UserStatus.Pending &amp;&amp; u.CreatedAtUtc &lt; cutoff)
            .ToListAsync();

        if (ghosts.Count == 0)
            return;

        _db.Users.RemoveRange(ghosts);
        await _db.SaveChangesAsync();

        _logger.LogInformation("Deleted {Count} ghost users older than {Cutoff}", ghosts.Count, cutoff);
    }
}
</code></pre></div><p>Register it and give it a schedule:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;e78fa68e-b76d-4644-837d-9196bb46a191&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">builder.Services.AddQuartz(q =&gt;
{
    var jobKey = new JobKey("GhostUserCleanupJob");

    q.AddJob&lt;GhostUserCleanupJob&gt;(opts =&gt; opts.WithIdentity(jobKey));

    q.AddTrigger(opts =&gt; opts
        .ForJob(jobKey)
        .WithIdentity("GhostUserCleanupJob-trigger")
        .WithSimpleSchedule(s =&gt; s
            .WithIntervalInMinutes(15)
            .RepeatForever()));
});

builder.Services.AddQuartzHostedService(q =&gt; q.WaitForJobsToComplete = true);
</code></pre></div><p>Every 15 minutes, this job deletes anyone who registered more than 30 minutes ago and never verified. Adjust the window to whatever fits your product: 30 minutes if OTP abandonment should be punished quickly, 30 days if you want a longer window before wiping people out. Google takes the long-window approach, deleting inactive accounts after two years of no activity. The principle scales, only the clock changes.</p><div><hr></div><h2>When Two Tables Is the Better Call</h2><p>If <code>users</code> gets hit by dozens of queries across multiple services, a status flag becomes a tax every one of those queries has to pay, either by filtering on <code>Status = Active</code> explicitly, or by risking a ghost row leaking into a report.</p><p>Option 2 avoids that entirely:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;035d40b5-ab32-4053-aca5-0b0b623644bc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">// On successful OTP verification:
var pending = await db.PendingUsers.FirstAsync(p =&gt; p.Email == request.Email);

var user = new User
{
    Id = Guid.NewGuid(),
    Email = pending.Email,
    PasswordHash = pending.PasswordHash,
    CreatedAtUtc = DateTime.UtcNow
};

db.Users.Add(user);
db.PendingUsers.Remove(pending);

await db.SaveChangesAsync();
</code></pre></div><p><code>users</code> never has a <code>Pending</code> row in it, ever. The trade-off is that verification now touches two tables in one transaction instead of flipping a column. For most teams starting out, that extra complexity isn&#8217;t worth paying for yet. Reach for it when your <code>users</code> table is under enough query pressure that &#8220;just filter by status&#8221; stops being a satisfying answer.</p><div><hr></div><h2>Bonus: What I&#8217;d Improve With More Time</h2><p>Stepping back from the tutorial for a second, here&#8217;s what I&#8217;d tighten up before this went anywhere near a real production system:</p><ul><li><p><strong>Decouple OTP sending from user creation with a domain event.</strong> Fire something like a <code>UserRegisteredEvent</code> inside the same transaction that inserts the pending user, and let a separate handler call the OTP provider. A slow or failing email or SMS provider should never be able to block or roll back the write that creates the row.</p></li><li><p><strong>Give that event real delivery guarantees.</strong> Wrapping it in the outbox pattern (I walked through this in <a href="https://mwaseemzakir.substack.com/p/implementing-the-outbox-pattern">The Outbox Pattern in .NET</a>) means a network blip on the OTP provider doesn&#8217;t lose the message, it just gets picked up and retried on the next processor run.</p></li><li><p><strong><a href="https://mwaseemzakir.substack.com/p/ep-29-rate-limiting-middleware-in">Rate limit</a> </strong><code>/register</code><strong> and </strong><code>/verify-otp</code><strong>.</strong> Nothing in this walkthrough stops someone from hitting either endpoint repeatedly with the same email, generating a fresh OTP every few seconds or brute-forcing the code. Both routes need a rate limiter keyed on email or IP before this ships.</p></li><li><p><strong>Make the cleanup job safe to run on more than one instance.</strong> If this ever scales out, two schedulers can grab the same batch of ghost rows at once. A claim-then-delete pattern using <code>SELECT ... FOR UPDATE SKIP LOCKED</code>, the same trick from the outbox processor, avoids double-processing.</p></li></ul><div><hr></div><h2>Summary</h2><ul><li><p>Don&#8217;t insert a user as if they&#8217;re real before they&#8217;ve verified their OTP, model it as <code>Pending</code> first.</p></li><li><p>A partial index on the pending rows keeps your cleanup job&#8217;s scan cheap as the table grows.</p></li><li><p>A Quartz job on a schedule hard-deletes anyone who never verified within your chosen window.</p></li><li><p>Two tables is the right call once your <code>users</code> table is under heavy query load from multiple services, not before.</p></li></ul><p>One status column. One background job. Zero ghosts.</p><div><hr></div><p><strong>Whenever you&#8217;re ready, there are 2 ways I can help you:</strong></p><ol><li><p>Boost your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ">YouTube Channel</a></strong></p></li><li><p><strong>Promote yourself to 10,000+ subscribers</strong> by sponsoring this newsletter</p></li></ol>]]></content:encoded></item><item><title><![CDATA[EF Core Interceptors: Stop Writing the Same Code in Every Handler]]></title><description><![CDATA[Soft delete, audit trails, and outbox inserts without touching a single handler. Here is how to set it up in .NET]]></description><link>https://mwaseemzakir.substack.com/p/ef-core-interceptors-soft-delete-audit-outbox-aspnet</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/ef-core-interceptors-soft-delete-audit-outbox-aspnet</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 18 Jul 2026 04:00:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5UH8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5UH8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5UH8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!5UH8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!5UH8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!5UH8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5UH8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:76185,&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://mwaseemzakir.substack.com/i/199243707?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.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_!5UH8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!5UH8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!5UH8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!5UH8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48d49bef-a3ff-43cd-9917-2fb4e2547a1b_1200x600.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 aria-hidden="true" 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><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>EF Core interceptors let you hook into database operations automatically, eliminating repetitive infrastructure logic across every handler. Instead of writing the same soft delete, audit trail, or outbox logic in dozens of places, you write it once and EF Core runs it on every operation.</p><p>Every time you save data, something else needs to happen.</p><p>A deleted record should not actually delete.</p><p>A created record should log who made it.</p><p>A domain event should land in the outbox table.</p><p>So you write that logic. In one handler. Then another. Then ten more.</p><p>Until one day you forget. And the bug is silent.</p><p>This is the problem interceptors solve.</p><p>If you have read my previous issue on <a href="https://mwaseemzakir.com/blog/built-in-middleware-in-asp-net-core">Built-in Middleware in ASP.NET Core</a>, you already understand the idea of hooking into a pipeline. Interceptors are the same concept, but for your DbContext.</p><h2>What is an Interceptor?</h2><p>EF Core interceptors let you hook into database operations before or after they happen.</p><p>Instead of repeating cross-cutting logic in every handler, you register it once and EF Core runs it automatically on every operation.</p><p>Think of it as middleware for your database context. Every time <code>SaveChangesAsync</code> runs, your interceptor gets called first. You inspect what&#8217;s about to be saved, modify it if needed, then let the operation continue.</p><p>This turns scattered infrastructure code into a single, centralized implementation.</p><div><hr></div><h2>Example 1: Soft Delete</h2><h3>The Pain</h3><p>Here is what soft delete looks like without interceptors:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;92962dae-fbc7-4f38-847e-f93e73d5bb43&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(DeleteBlogCommand command, CancellationToken ct)
{
    var blog = await context.Blogs.FindAsync(command.Id);

    blog.IsDeleted = true;
    blog.DeletedAt = DateTime.UtcNow;

    await context.SaveChangesAsync(ct);
}</code></pre></div><p>Fine for one handler. But now you have users, comments, posts, categories. All need the same logic.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;5870b89f-ab2e-4255-9e4c-37d7ee09f7c7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(DeleteUserCommand command, CancellationToken ct)
{
    var user = await context.Users.FindAsync(command.Id);

    user.IsDeleted = true;
    user.DeletedAt = DateTime.UtcNow;

    await context.SaveChangesAsync(ct);
}

public async Task Handle(DeleteCommentCommand command, CancellationToken ct)
{
    var comment = await context.Comments.FindAsync(command.Id);

    comment.IsDeleted = true;
    comment.DeletedAt = DateTime.UtcNow;

    await context.SaveChangesAsync(ct);
}</code></pre></div><p>You will forget one. Or a junior developer will call <code>context.Remove()</code> and bypass it entirely.</p><p>Now let us fix this properly.</p><h3>The Fix</h3><p>Interceptors solve this by hooking into <code>SaveChangesAsync</code> before any data touches the database. When EF Core detects a deleted entity, the interceptor changes the operation from delete to update and sets the soft delete flags automatically.</p><p>Create a class that extends <code>SaveChangesInterceptor</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;5af47202-60c6-4263-b518-3e002dd6e3bd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class SoftDeleteInterceptor : SaveChangesInterceptor
{
    public override ValueTask&gt; SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult result,
        CancellationToken ct = default)
    {
        if (eventData.Context is null)
            return base.SavingChangesAsync(eventData, result, ct);

        foreach (var entry in eventData.Context.ChangeTracker.Entries())
        {
            if (entry is { State: EntityState.Deleted, Entity: ISoftDeletable entity })
            {
                entry.State = EntityState.Modified;
                entity.IsDeleted = true;
                entity.DeletedAt = DateTime.UtcNow;
            }
        }

        return base.SavingChangesAsync(eventData, result, ct);
    }
}</code></pre></div><p>Add a marker interface:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;5ea3099f-df14-4a7f-8a19-6c1fe3ef1c9d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public interface ISoftDeletable
{
    bool IsDeleted { get; set; }
    DateTime? DeletedAt { get; set; }
}</code></pre></div><p>Implement it on your entity:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;03882b83-de48-4df3-a9f4-1b74b1710a3f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class Blog : ISoftDeletable
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public bool IsDeleted { get; set; }
    public DateTime? DeletedAt { get; set; }
}</code></pre></div><p>Now when anyone calls <code>context.Remove(blog)</code>, EF Core intercepts it and flips <code>IsDeleted</code> instead of deleting the row.</p><p>Your handler goes back to being simple:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;de451377-c99b-49bd-a951-7e1873590e24&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(DeleteBlogCommand command, CancellationToken ct)
{
    var blog = await context.Blogs.FindAsync(command.Id);
    context.Blogs.Remove(blog);
    await context.SaveChangesAsync(ct);
}</code></pre></div><p>The interceptor handles the rest. Every entity that implements <code>ISoftDeletable</code> gets soft deleted automatically. No repeated logic. No forgotten flags.</p><p>Register it in your DbContext setup:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;dce0ed48-9449-4c84-b9b1-3400e3d084ff&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">options.UseNpgsql(connectionString)
       .AddInterceptors(new SoftDeleteInterceptor());</code></pre></div><div><hr></div><h2>Example 2: Audit Entries</h2><h3>The Pain</h3><p>Here is what tracking who-did-what looks like without interceptors:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;e2f5f672-ce37-4741-81b1-571bfa6e048c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(CreateBlogCommand command, CancellationToken ct)
{
    var blog = new Blog
    {
        Id = Guid.NewGuid(),
        Title = command.Title,
        CreatedAt = DateTime.UtcNow,
        CreatedBy = _currentUser.UserId
    };

    context.Blogs.Add(blog);
    await context.SaveChangesAsync(ct);
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;cbf46287-ee47-4999-8dfa-2e65f6bc3a54&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(UpdateBlogCommand command, CancellationToken ct)
{
    var blog = await context.Blogs.FindAsync(command.Id);

    if (blog is null)
        throw new NotFoundException(nameof(Blog), command.Id);

    blog.Title = command.Title;
    blog.UpdatedAt = DateTime.UtcNow;
    blog.UpdatedBy = _currentUser.UserId;

    await context.SaveChangesAsync(ct);
}</code></pre></div><p>Every create handler needs <code>CreatedAt</code> and <code>CreatedBy</code>. Every update handler needs <code>UpdatedAt</code> and <code>UpdatedBy</code>.</p><p>Across users, posts, comments, categories, that is a lot of places to remember four lines of code.</p><p>Miss one, and your audit trail has a silent gap. You will not notice until someone asks &#8220;who changed this?&#8221; and you have no answer.</p><h3>The Fix</h3><p>Interceptors solve this by inspecting the change tracker before save and automatically stamping audit fields based on the entity state. Added entities get creation timestamps. Modified entities get update timestamps.</p><p>Same pattern as soft delete. Different interface.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;46e12538-c357-41ad-bedb-269599ea0e0a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public interface IAuditable
{
    DateTime CreatedAt { get; set; }
    DateTime? UpdatedAt { get; set; }
    string CreatedBy { get; set; }
    string? UpdatedBy { get; set; }
}</code></pre></div><p>Create the interceptor:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;d5bae50a-3bef-47f3-bd4d-1cd8f1f436e8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class AuditInterceptor : SaveChangesInterceptor
{
    private readonly ICurrentUserService _currentUser;

    public AuditInterceptor(ICurrentUserService currentUser)
    {
        _currentUser = currentUser;
    }

    public override ValueTask&gt; SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult result,
        CancellationToken ct = default)
    {
        if (eventData.Context is null)
            return base.SavingChangesAsync(eventData, result, ct);

        var now = DateTime.UtcNow;
        var user = _currentUser.UserId;

        foreach (var entry in eventData.Context.ChangeTracker.Entries())
        {
            if (entry.State == EntityState.Added)
            {
                entry.Entity.CreatedAt = now;
                entry.Entity.CreatedBy = user;
            }

            if (entry.State == EntityState.Modified)
            {
                entry.Entity.UpdatedAt = now;
                entry.Entity.UpdatedBy = user;
            }
        }

        return base.SavingChangesAsync(eventData, result, ct);
    }
}</code></pre></div><p>Implement on your entity:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;645bbeda-80d8-4811-a49d-16a390097c3e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class Blog : IAuditable
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
    public string CreatedBy { get; set; }
    public string? UpdatedBy { get; set; }
}</code></pre></div><p>Your handlers go back to just being handlers:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;82e44533-ba9d-4a42-ae1d-86ef4fc9e656&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(CreateBlogCommand command, CancellationToken ct)
{
    var blog = new Blog
    {
        Id = Guid.NewGuid(),
        Title = command.Title
    };

    context.Blogs.Add(blog);
    await context.SaveChangesAsync(ct);
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;24dc175e-37f8-487e-8a54-012396e8610f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(UpdateBlogCommand command, CancellationToken ct)
{
    var blog = await context.Blogs.FindAsync(command.Id);

    if (blog is null)
        throw new NotFoundException(nameof(Blog), command.Id);

    blog.Title = command.Title;

    await context.SaveChangesAsync(ct);
}</code></pre></div><p>Every insert and update now tracks who did it and when. Zero extra code in your handlers.</p><p>This pairs well with the <a href="https://mwaseemzakir.com/blog/clean-code-tips-for-dotnet-developers-part-i">Clean Code Tips I</a> covered earlier. Keeping handlers focused on business logic and pushing infrastructure concerns like auditing into interceptors is exactly what clean code looks like in practice.</p><div><hr></div><h2>Example 3: Outbox Message Insert</h2><h3>The Pain</h3><p>If you don&#8217;t know what the <a href="https://mwaseemzakir.com/blog/clean-code-tips-for-dotnet-developers-part-i">Outbox Pattern</a> is, I have a full article on implementing <a href="https://mwaseemzakir.com/blog/clean-code-tips-for-dotnet-developers-part-i">outbox pattern in .NET</a> &#8212; this example assumes the basic idea: when you change state, you write an event to an &#8220;outbox&#8221; table in the same transaction, so a background process can dispatch it reliably later.</p><p>Without an interceptor, every handler that raises a domain event has to build and insert the outbox row itself:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;97fcdde3-e77c-4c7a-a3ff-af1798f3cbd7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(DeleteUserCommand command, CancellationToken ct)
{
    var user = await context.Users.FindAsync(command.Id);
    user.Delete();

    var domainEvent = new UserDeletedEvent(user.Id);

    var outboxMessage = new OutboxMessage
    {
        Id = Guid.NewGuid(),
        Type = domainEvent.GetType().AssemblyQualifiedName!,
        Payload = JsonSerializer.Serialize(domainEvent, domainEvent.GetType()),
        CreatedAt = DateTime.UtcNow
    };

    context.Set().Add(outboxMessage);
    await context.SaveChangesAsync(ct);
}</code></pre></div><p>This is the most dangerous one to forget.</p><p>Miss the soft-delete flag and a row that should be gone is still visible annoying, but obvious.</p><p>Miss the outbox insert and nothing happens. No exception. No log. A downstream service just never finds out the user was deleted, and you won&#8217;t know until someone notices the data drifted apart.</p><h3>The Fix</h3><p>Interceptors solve this by scanning the change tracker for entities that raised domain events, converting those events into outbox messages, and inserting them automatically in the same transaction.</p><p>This is the most powerful use case for interceptors.</p><p>First, your base entity raises events:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;060890a8-635d-49be-9da3-dc1d46129ce1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public abstract class Entity
{
    private readonly List _domainEvents = new();

    public IReadOnlyList DomainEvents =&gt; _domainEvents;

    protected void Raise(IDomainEvent domainEvent) =&gt;
        _domainEvents.Add(domainEvent);

    public void ClearDomainEvents() =&gt;
        _domainEvents.Clear();
}</code></pre></div><p>Now the interceptor picks them up before save:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;a9873bb0-19f6-4450-b5ba-4556a8f9278e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class OutboxInterceptor : SaveChangesInterceptor
{
    public override ValueTask&gt; SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult result,
        CancellationToken ct = default)
    {
        if (eventData.Context is null)
            return base.SavingChangesAsync(eventData, result, ct);

        var outboxMessages = eventData.Context.ChangeTracker
            .Entries()
            .SelectMany(e =&gt; e.Entity.DomainEvents)
            .Select(domainEvent =&gt; new OutboxMessage
            {
                Id = Guid.NewGuid(),
                Type = domainEvent.GetType().AssemblyQualifiedName!,
                Payload = JsonSerializer.Serialize(domainEvent, domainEvent.GetType()),
                CreatedAt = DateTime.UtcNow
            })
            .ToList();

        eventData.Context.Set().AddRange(outboxMessages);

        foreach (var entry in eventData.Context.ChangeTracker.Entries())
            entry.Entity.ClearDomainEvents();

        return base.SavingChangesAsync(eventData, result, ct);
    }
}</code></pre></div><p>Now your handler looks like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;edd71153-02b5-4af0-8db9-ee3791783e96&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(DeleteUserCommand command, CancellationToken ct)
{
    var user = await context.Users.FindAsync(command.Id);
    user.Delete();
    await context.SaveChangesAsync(ct);
}</code></pre></div><p><code>user.Delete()</code> raises a <code>UserDeletedEvent</code>. The interceptor catches it and inserts the outbox row using <code>AssemblyQualifiedName</code>, the same convention I used in the outbox article, so <code>Type.GetType()</code> resolves correctly when the background processor picks it up.</p><p>Your handler knows nothing about any of this.</p><p>Once the outbox message is saved, something needs to process it. I use <a href="https://mwaseemzakir.com/blog/wolverine-as-mediator-in-dotnet">Wolverine as a Mediator</a> for exactly this. Wolverine picks up outbox messages and dispatches them reliably without you wiring anything manually.</p><div><hr></div><h2>Registering Multiple Interceptors</h2><p>You can stack them all:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;2c097f28-ce92-48af-82b9-9e32877eae38&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">options.UseNpgsql(connectionString)
       .AddInterceptors(
           new SoftDeleteInterceptor(),
           new AuditInterceptor(currentUserService),
           new OutboxInterceptor()
       );</code></pre></div><p>EF Core runs them in the order you register them. If one interceptor modifies entity state that another interceptor needs to see, order matters.</p><div><hr></div><h2>When to Use Interceptors</h2><p>Use interceptors when the same infrastructure logic needs to run across many entities without touching individual handlers.</p><p><strong>Good use cases:</strong></p><ul><li><p>Soft delete across all entities</p></li><li><p>Audit trails (CreatedBy, UpdatedBy, timestamps)</p></li><li><p>Outbox message inserts for domain events</p></li><li><p>Row-level security (setting TenantId automatically)</p></li><li><p>Optimistic concurrency (auto-incrementing version numbers)</p></li><li><p>Global query filters (excluding soft-deleted records from queries)</p></li></ul><p>These are all infrastructure concerns. They have nothing to do with your business rules. They need to happen consistently everywhere, and forgetting them creates silent bugs.</p><p><strong>Do NOT use interceptors for:</strong></p><ul><li><p>Business validation (e.g., &#8220;a blog post must have a title&#8221;)</p></li><li><p>Domain rules (e.g., &#8220;a user can only delete their own comments&#8221;)</p></li><li><p>Feature-specific logic (e.g., &#8220;send a welcome email when a user registers&#8221;)</p></li><li><p>Conditional behavior based on the command being executed</p></li></ul><p>If the logic is specific to one feature or depends on business context, it belongs in the handler. Interceptors are for cross-cutting infrastructure that applies universally.</p><p><strong>A simple test:</strong> If you would need to explain <em>why</em> this logic runs to a product manager, it does not belong in an interceptor. If it is just &#8220;how the system works under the hood,&#8221; it probably does.</p><div><hr></div><h2>Why This Matters</h2><p>Interceptors change the way you think about handlers and infrastructure separation.</p><p>Your handlers become pure expressions of business intent. They do not care about audit trails, soft deletes, or outbox tables. They just change state and save. The infrastructure takes care of itself.</p><p>This is not just cleaner code. It is safer code. You cannot forget to log an audit entry if the system does it automatically. You cannot skip the outbox insert if the interceptor handles it for every save.</p><p>When infrastructure concerns live in one place, they are easier to test, easier to change, and impossible to forget.</p><div><hr></div><h2>Demo Code</h2><p>I put together a complete running example with all three interceptors wired up.</p><p>Clone it, run it, and see how clean your handlers look when the infrastructure takes care of itself.</p><p>&#128073; <strong><a href="https://github.com/Mwaseemzakir/Newsletter">GitHub Issue #78</a></strong></p><div><hr></div><h2>There are 2 ways I can help you:</h2><p><strong>Enhance your .NET skills</strong> by subscribing to my <a href="#">YouTube Channel</a></p><p><strong>Promote yourself</strong> to 10,000+ subscribers by <a href="#">sponsoring this newsletter</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[The Outbox Pattern in .NET]]></title><description><![CDATA[Learn how to implement the Outbox Pattern in ASP .NET Core using EF Core, background jobs, and retry logic.]]></description><link>https://mwaseemzakir.substack.com/p/implementing-the-outbox-pattern</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/implementing-the-outbox-pattern</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 11 Jul 2026 04:01:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!AHdz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.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_!AHdz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AHdz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!AHdz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!AHdz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!AHdz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AHdz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:70062,&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://mwaseemzakir.substack.com/i/203827724?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.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_!AHdz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!AHdz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!AHdz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!AHdz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5abb7c4-29a6-4122-b2e5-8a68f52164c1_1200x600.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 aria-hidden="true" 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><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><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><p>Your handler saved the data. But the email never sent. The notification never fired. And you have no idea why.</p><p>You have seen this bug before.</p><p>Everything looks fine. The record is in the database. But something that was supposed to happen after the save just did not.</p><p>Maybe the API crashed mid-request. Maybe an exception swallowed the side effect. Maybe it just ran out of memory.</p><p>This is not a code bug. It is an architectural gap. And the Outbox Pattern is how you close it.</p><div><hr></div><h2>What is the Outbox Pattern?</h2><p>The idea is simple.</p><p>Instead of firing side effects directly after a save, you write them to a database table in the same transaction as your main data.</p><p>A background job then picks them up and processes them reliably.</p><p><strong>Without Outbox:</strong></p><p><code>Save User &#8594; Send Welcome Email</code> &#8592; email can fail silently</p><p><strong>With Outbox:</strong></p><p><code>(Save User + Insert Outbox Message) &#8594; Background Job &#8594; Send Welcome Email</code></p><p>The key word is <em>same transaction</em>. If the save fails, the outbox message never gets inserted. If the save succeeds, the message is guaranteed to be processed eventually.</p><div><hr></div><h2>The Problem Without It</h2><p>Here is what most developers write first:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;adf0ad17-717d-4c26-b8ef-c723a5753dc4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(RegisterUserCommand command, CancellationToken ct)
{
    var user = User.Create(command.Email);
    context.Users.Add(user);
    await context.SaveChangesAsync(ct);
    await emailService.SendWelcomeEmailAsync(user.Email, ct);
}</code></pre></div><p>This looks fine. But there are two problems.</p><p>If <code>SaveChangesAsync</code> throws, the email does not send. Fine, that is expected.</p><p>But if <code>SendWelcomeEmailAsync</code> throws <em>after</em> the save, your user is created and never gets the welcome email. And you will never know unless you check manually. Now imagine that email was an OTP for account verification the user is stuck and can never finish signing up.</p><p>Multiply this across ten features. Password resets, order confirmations, notification triggers. Every single one is a silent failure waiting to happen.</p><div><hr></div><h2>Setting Up the Outbox Table</h2><p>Start with the entity:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;465f3aac-a23f-4d04-a3b2-d13bf29df79a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class OutboxMessage
{
    public Guid Id { get; set; }
    public string Type { get; set; }
    public string Payload { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? ProcessedAt { get; set; }
    public string? Error { get; set; }
    public int RetryCount { get; set; }
}</code></pre></div><p>Apply it via migration:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;cc8f577d-bc21-41cd-a2b9-3fc6da754c3d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">CREATE TABLE OutboxMessages (
    Id UUID PRIMARY KEY,
    Type VARCHAR(255) NOT NULL,
    Payload TEXT NOT NULL,
    CreatedAt TIMESTAMP NOT NULL,
    ProcessedAt TIMESTAMP NULL,
    Error TEXT NULL,
    RetryCount INT NOT NULL DEFAULT 0
);

-- Your background job will run "WHERE ProcessedAt IS NULL ORDER BY CreatedAt" constantly.
-- A partial index keeps that query fast even after the table has millions of processed rows.
CREATE INDEX IX_OutboxMessages_Unprocessed
ON OutboxMessages (CreatedAt)
WHERE ProcessedAt IS NULL;</code></pre></div><p>That partial index only covers unprocessed rows, so it stays small and cheap to scan no matter how large the table gets.</p><blockquote><p><span>A </span><em>partial index</em><span> is an index built over a subset of a table; the subset is defined by a conditional expression (called the </span><em>predicate</em><span> of the partial index). The index contains entries only for those table rows that satisfy the predicate. Partial indexes are a specialized feature, but there are several situations in which they are useful.</span></p></blockquote><div><hr></div><h2>Writing to the Outbox</h2><p>Without using the outbox our code would look like this normally :</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;367ebe99-8b1d-4eca-bb52-39542043be0f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(RegisterUserCommand command, CancellationToken ct)
{
    var user = User.Create(command.Email);
    context.Users.Add(user);
    await context.SaveChangesAsync(ct);
    
   // Separate operation &#8212; no guarantee this runs
    await emailService.SendWelcomeEmailAsync(user.Email, ct);
}</code></pre></div><p>Instead, write the event to the outbox table in the same transaction as your business data:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;09352901-8ee8-4e20-9db7-5ef34a83c854&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public async Task Handle(RegisterUserCommand command, CancellationToken ct)
{
    var user = User.Create(command.Email);
    var welcomeEmail = new WelcomeEmailRequested(user.Email);
    var outboxMessage = new OutboxMessage
    {
        Id = Guid.NewGuid(),
        Type = typeof(WelcomeEmailRequested).AssemblyQualifiedName!,
        Payload = JsonSerializer.Serialize(welcomeEmail),
        CreatedAt = DateTime.UtcNow
    }; 
    context.Users.Add(user);
    context.OutboxMessages.Add(outboxMessage); 
    await context.SaveChangesAsync(ct);
}</code></pre></div><p>No email call. No notification call. Just save and done.</p><p>Both the user and the outbox message are written atomically. If the transaction rolls back, both roll back. Nothing is lost.</p><blockquote><p><strong>A small but important detail:</strong> store <code>AssemblyQualifiedName</code>, not just the type&#8217;s short name (<code>nameof(WelcomeEmailRequested)</code> gives you <code>"WelcomeEmailRequested"</code> with no namespace or assembly info). Your background job needs to turn that string back into a real <code>Type</code> to deserialize the payload and <code>Type.GetType(...)</code> can only do that reliably if the string carries the full assembly info. Skip this and your job will throw <code>InvalidOperationException: Unknown event type</code> the first time it runs.</p></blockquote><div><hr></div><h2>Processing the Outbox</h2><p>A background job polls the table and processes unhandled messages:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;50f77d58-b687-421a-af3b-911122914ddc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">[DisallowConcurrentExecution]
internal sealed class ProcessOutboxMessagesJob(AppDbContext dbContext, IMessageBus messageBus) : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        var cancellationToken = context.CancellationToken;

        await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);

        var messages = await dbContext.OutboxMessages
            .FromSqlRaw(
                """
                SELECT "Id", "Type", "Payload", "CreatedAt", "ProcessedAt", "Error", "RetryCount"
                FROM "OutboxMessages"
                WHERE "ProcessedAt" IS NULL
                ORDER BY "CreatedAt"
                LIMIT 20
                FOR UPDATE SKIP LOCKED
                """)
            .ToListAsync(cancellationToken);

        foreach (var message in messages)
        {
            try
            {
                var eventType = Type.GetType(message.Type)
                    ?? throw new InvalidOperationException($"Unknown event type '{message.Type}'.");

                var @event = JsonSerializer.Deserialize(message.Payload, eventType)
                    ?? throw new InvalidOperationException($"Could not deserialize payload for '{message.Type}'.");

                await messageBus.InvokeAsync(@event, cancellationToken);

                message.ProcessedAt = DateTime.UtcNow;
                message.Error = null;
            }
            catch (Exception ex)
            {
                message.Error = ex.Message;
                message.RetryCount++;
            }
        }

        await dbContext.SaveChangesAsync(cancellationToken);
        await transaction.CommitAsync(cancellationToken);
    }
}</code></pre></div><p>The job runs every 10 seconds. It fetches up to 20 unprocessed messages, publishes them, and marks them processed. Oldest first, always.</p><p>If you&#8217;re already using Wolverine as your mediator (see my <a href="https://mwaseemzakir.substack.com/p/wolverine-as-mediator-in-net">Wolverine as Mediator in .NET</a> piece), <code>IMessageBus.InvokeAsync</code> here is the exact same call you&#8217;d use to dispatch a command in your handlers the outbox job is just another caller.</p><div><hr></div><h2>At-Least-Once Delivery: Your Consumers Must Be Idempotent</h2><p>Some points worth considering, the outbox pattern does not guarantee a message is processed <em>exactly once</em>. It guarantees <em>at least once</em>.</p><p>Think about what happens if the job publishes the message successfully, then crashes before it can set <code>ProcessedAt</code> and commit. The next run picks up that same message and publishes it again.</p><p>That means <code>SendWelcomeEmailAsync</code> or whatever handler consumes the message needs to be safe to run twice. A few practical ways to get there:</p><ul><li><p>Check whether the action already happened before doing it again (e.g., &#8220;has this user already received a welcome email?&#8221;).</p></li><li><p>Use a natural or generated idempotency key so duplicate sends are detected and skipped downstream.</p></li><li><p>For anything transactional (charging a card, decrementing stock), make the operation itself idempotent rather than relying on the outbox to dedupe for you.</p></li></ul><h2>Preventing Duplicate Processing</h2><p>If two instances of your app run at the same time, both might pick up the same message.</p><p>Use <code>FOR UPDATE SKIP LOCKED</code> to prevent that:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;cdfda443-f88e-4674-bd50-aa184f4885d8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">SELECT * FROM "OutboxMessages"
WHERE "ProcessedAt" IS NULL
ORDER BY "CreatedAt"
LIMIT 20
FOR UPDATE SKIP LOCKED</code></pre></div><p>Now only one instance processes each message. The other skips it and moves on.</p><p>Note this solves <em>concurrent</em> duplicate processing across instances it does not replace the idempotency work above, which covers crash-and-retry duplicates on a single instance.</p><div><hr></div><h2>Keeping the Outbox Table Healthy</h2><p>Nobody thinks about this until the table has ten million rows and the background job starts timing out.</p><p>Two habits keep it under control:</p><ol><li><p><strong>Cap retries.</strong> I have added a column named <code>RetryCount </code>in our entity use it for retries count. After a handful of failed attempts (e.g. 3 attempts), stop retrying automatically and flag the message for manual review instead of hammering a dependency that&#8217;s clearly down.</p></li><li><p><strong>Archive or delete processed rows.</strong> Once a message is processed and you&#8217;re past the window where you&#8217;d ever need to debug it, delete it (or move it to a cold-storage table):</p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;44b5d66b-dcf9-4030-9ea3-50afb295b3d8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">DELETE FROM "OutboxMessages"
WHERE "ProcessedAt" IS NOT NULL
AND "ProcessedAt" &lt; NOW() - INTERVAL '7 days';</code></pre></div><p>Run this as its own scheduled job, separate from the processor. A table that only ever grows will eventually make even the partial index expensive to maintain.</p><div><hr></div><p><span>&#128073; Find complete running demo code at </span><a href="https://github.com/Mwaseemzakir"><span>GitHub Issue #77</span></a></p><h2>Summary</h2><p>Here is everything covered in this issue:</p><ul><li><p>The problem: saving data and calling side effects separately creates a silent failure gap.</p></li><li><p>The fix: write your messages to a database table in the same transaction as your business data, then process them asynchronously.</p></li><li><p><code>OutboxMessage</code> entity stores the event type (as an assembly-qualified name), serialized payload, and processed timestamp.</p></li><li><p>Write the outbox message directly in your handler alongside your business data in one <code>SaveChangesAsync</code> call.</p></li><li><p>A background job polls every 10 seconds, deserializes each message, publishes it, and marks it processed.</p></li><li><p><code>FOR UPDATE SKIP LOCKED</code> prevents duplicate processing across multiple app instances.</p></li><li><p>Outbox delivery is at-least-once your consumers must be idempotent.</p></li><li><p>A partial index and a cleanup job keep the table fast as it grows.</p></li></ul><p>One table. One background job. Zero lost events.</p><h3><strong>There are 3 ways I can help you:</strong></h3><ol><li><p><span>Enhance your .NET skills by subscribing to my </span><strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 10,000+ subscribers</a><span> </span></strong><span>by sponsoring this newsletter</span></p></li><li><p>Have a software idea?<strong> </strong>Let&#8217;s turn it into a real product, <strong><a href="https://mwaseemzakir.com/work-with-me">Work With Me</a></strong></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Build AI Search in .NET using Postgres and pgvector]]></title><description><![CDATA[Learn how to build AI-powered search in ASP .NET Core using PostgreSQL, pgvector, embeddings, and with a practical example.]]></description><link>https://mwaseemzakir.substack.com/p/build-ai-search-in-net-using-postgres</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/build-ai-search-in-net-using-postgres</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 04 Jul 2026 04:00:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!KBA_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KBA_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KBA_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!KBA_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!KBA_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!KBA_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KBA_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:72363,&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://mwaseemzakir.substack.com/i/203555849?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.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_!KBA_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!KBA_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!KBA_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!KBA_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b00ea2-2e0e-42d6-b78f-e5cb36e64ae9_1200x600.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 aria-hidden="true" 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><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>&#8220;Search is easy... until users stop searching with the exact words you stored.&#8221;</p><p>Imagine you have thousands of blog posts.</p><p>A user searches:</p><p>&#8220;How do I secure my APIs?&#8221;</p><p>But your blog title is:</p><p>&#8220;JWT Authentication in ASP.NET Core&#8221;</p><p>A normal database search returns nothing.</p><p>Even though that article is exactly what the user wants.</p><p>This is the problem with keyword search and it is why AI-powered search exists.</p><p>In this article, we will build an AI-powered search using:</p><ul><li><p>ASP.NET Core</p></li><li><p>PostgreSQL</p></li><li><p>pgvector</p></li><li><p>Gemini Embeddings API</p></li></ul><p>Let&#8217;s break it down step by step.</p><div><hr></div><h2>The Problem With Normal Search</h2><p>Here is what normal search looks like in EF Core:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;8a3d5997-f878-4dfd-b869-e4de5ad68e81&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">var blogs = await context.Blogs
    .Where(x =&gt;
        x.Title.Contains(search) ||
        x.Description.Contains(search))
    .ToListAsync();</code></pre></div><p>This works fine when users search with the exact words stored in your database.</p><p>But when they search by meaning, &#8220;secure my APIs&#8221; instead of &#8220;JWT&#8221;, it fails silently.</p><p>You get zero results. Not because the data does not exist, but because the search does not understand meaning.</p><p>That is what vector search solves.</p><div><hr></div><h2>What is Vector Search?</h2><p>Vector Search is a way of searching by meaning instead of exact words.</p><p>Instead of asking:</p><p>&#8220;Does this sentence contain JWT?&#8221;</p><p>It asks:</p><p>&#8220;Does this sentence mean something similar to what the user asked?&#8221;</p><div><hr></div><h2>What are Embeddings?</h2><p>Embeddings are numbers that represent the meaning of text.</p><p>A sentence like:</p><p>&#8220;JWT Authentication in ASP.NET Core&#8221;</p><p>becomes something like:</p><pre><code><code>[0.123, -0.873, ...]</code></code></pre><p>Text with similar meanings produces similar vectors. That is how meaning-based search works.</p><div><hr></div><h2>Step 1 &#8212; Generate Embeddings with Gemini</h2><p>Almost every AI provider offers an embedding model. Gemini, OpenAI, local LLMs.</p><p>With the Gemini free API, you send your text using the models e.g. gemini-embedding-001 :</p><pre><code>POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:embedContent";</code></pre><p>And you will receive a response similar to this :</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;901141de-2368-445e-8f4d-b02eb5b667b5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{
  "embedding": [0.123, -0.873, ...]
}</code></pre></div><p>Store this vector in your database alongside the blog post.</p><div><hr></div><h2>Step 2 &#8212; Update Your Blog Entity</h2><p>Add an <code>Embedding</code> property to your <code>Blog</code> entity:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;37da08ee-2885-49e8-af58-186c9cf138a1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">using Pgvector;

public class Blog
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public Vector? Embedding { get; set; }   // New
}</code></pre></div><div><hr></div><h2>Step 3 &#8212; Configure PostgreSQL</h2><p>Enable the pgvector extension:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;55600fbf-4e86-4026-a00f-fbcc9bbb5824&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">CREATE EXTENSION vector;</code></pre></div><p>Add the vector column:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;75efa302-ce72-460f-a59e-2323b84b3ef9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">ALTER TABLE Blogs
ADD COLUMN Embedding vector(768);</code></pre></div><p>Install the EF Core package:</p><pre><code><code>dotnet add package Pgvector.EntityFrameworkCore</code></code></pre><p>Configure it in your <code>DbContext</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;22916d3f-15fb-4a92-80b8-fef6f4a09558&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">options.UseNpgsql(connectionString, o =&gt; o.UseVector());</code></pre></div><p>PostgreSQL now understands vectors.</p><div><hr></div><h2>Step 4 &#8212; Save Embeddings on Blog Creation</h2><p>Whenever a new blog is created, generate and save its embedding:</p><pre><code><code>Blog &#8594; Gemini Embedding API &#8594; Embedding Vector &#8594; Save Blog + Vector</code></code></pre><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;f2584bda-5930-41da-a6da-2ad5827d7a92&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">blog.Embedding = await embeddingService.GenerateAsync(blog.Description);

context.Blogs.Add(blog);

await context.SaveChangesAsync();
</code></pre></div><div><hr></div><h2>Step 5 &#8212; Search by Meaning</h2><p>When a user searches &#8220;Secure my APIs&#8221;:</p><p>Generate an embedding for the search query:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;846050ac-b29f-4c68-b436-83c225a5ae35&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">var queryEmbedding = await embeddingService.GenerateAsync(search);</code></pre></div><p>Then ask PostgreSQL to return the nearest vectors:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;92c1447e-ac61-4e01-ab66-156fbd3f5483&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">var blogs = await context.Blogs
    .OrderBy(x =&gt; x.Embedding.CosineDistance(queryEmbedding))
    .Take(5)
    .ToListAsync();</code></pre></div><p>Cosine distance measures how similar two vectors are in meaning.</p><p>The closest ones come back first, even if no keywords match.</p><div><hr></div><h2>Demo Code</h2><p>I put together a complete running Web API with 100 seeded articles.</p><p>Clone the repo, add your Gemini API key and PostgreSQL connection string and you are ready to go.</p><p>&#128073; <a href="https://github.com/Mwaseemzakir">GitHub Issue #76</a></p><div><hr></div><h2>What is Next?</h2><p>Vector search gets you the right documents.</p><p>But what if you want a direct answer instead of a list of results?</p><p>That is where RAG comes in. You take these search results and pass them to an LLM to generate a precise, grounded answer. We will cover that in the future articles.</p><div><hr></div><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p><span>Enhance your .NET skills by subscribing to my </span><strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 10,000+ subscribers</a><span> </span></strong><span>by sponsoring this newsletter</span></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Understanding Monorepos (And Why I Chose One for My SaaS)]]></title><description><![CDATA[Learn what a monorepo is, when to use it, why companies like Google prefer it, and how I use it to build my SaaS, PakExam]]></description><link>https://mwaseemzakir.substack.com/p/understanding-monorepos-and-why-i</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/understanding-monorepos-and-why-i</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 27 Jun 2026 04:01:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!tvET!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tvET!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tvET!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!tvET!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!tvET!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!tvET!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tvET!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:77100,&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://mwaseemzakir.substack.com/i/203545041?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.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_!tvET!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!tvET!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!tvET!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!tvET!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17607be6-4641-42c2-aa0d-866278a2e8dd_1200x600.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 aria-hidden="true" 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><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;ve been building software for a while, you&#8217;ve probably heard the term Monorepo.</p><p>But what exactly is it?</p><p>Let&#8217;s break it down.</p><div><hr></div><h2>What is a Monorepo?</h2><p>A Monorepo (Monolithic Repository) is a single Git repository that contains multiple projects.</p><p>As per the official definition :</p><blockquote><p>A monorepo (monolithic repository) is a software development strategy where code for multiple separate, independent projects or services is stored in a single repository</p></blockquote><p>Instead of creating separate repositories for your frontend, backend, mobile apps, shared libraries, and infrastructure, everything lives in one repository.</p><p>For example:</p><pre><code><code>pakexam/
&#9474;
&#9500;&#9472;&#9472; frontend/
&#9500;&#9472;&#9472; admin-panel/
&#9500;&#9472;&#9472; backend/
&#9474;   &#9500;&#9472;&#9472; API
&#9474;   &#9500;&#9472;&#9472; Application
&#9474;   &#9500;&#9472;&#9472; Domain
&#9474;   &#9500;&#9472;&#9472; Infrastructure
&#9474;   &#9492;&#9472;&#9472; Tests
&#9474;
&#9500;&#9472;&#9472; mobile/
&#9474;   &#9500;&#9472;&#9472; android/
&#9474;   &#9492;&#9472;&#9472; ios/
&#9474;
&#9500;&#9472;&#9472; shared/
&#9492;&#9472;&#9472; docs/
</code></code></pre><p>Everything is versioned together.</p><p>One commit can update multiple projects at the same time.</p><div><hr></div><h2>Monorepo vs Multiple Repositories</h2><p>In the opposite of monorepos we have option of multiple repos, let&#8217;s see how it looks in picture :</p><p>One repository, single source of truth.</p><pre><code><code>pakexam
&#9500;&#9472;&#9472; frontend
&#9500;&#9472;&#9472; backend
&#9500;&#9472;&#9472; admin
&#9500;&#9472;&#9472; mobile
&#9492;&#9472;&#9472; shared
</code></code></pre><p>On the other hand for multiple repos :</p><pre><code><code>pakexam-frontend
pakexam-backend
pakexam-admin
pakexam-mobile
pakexam-shared</code></code></pre><p>Each project has its own Git repository.</p><p>Both approaches are valid.</p><p>The right choice depends on your team and product.</p><div><hr></div><h2>When should you use a Monorepo?</h2><p>A monorepo works really well when:</p><ul><li><p>You&#8217;re building multiple applications that evolve together.</p></li><li><p>Frontend and backend are maintained by the same team or in my case you are the only developer.</p></li><li><p>You have shared code or libraries.</p></li><li><p>Changes often span multiple projects.</p></li><li><p>You want everything versioned together.</p></li></ul><p>For example, imagine you rename an API endpoint.</p><p>With a monorepo, you can update:</p><ul><li><p>Backend</p></li><li><p>Frontend</p></li><li><p>Mobile</p></li><li><p>Shared SDK</p></li><li><p>Tests</p></li></ul><p>...all in a single commit.</p><p>No synchronization headaches.</p><div><hr></div><h2>When should you avoid it?</h2><p>A monorepo isn&#8217;t always the right answer.</p><p>It can become difficult when:</p><ul><li><p>Hundreds of developers work on unrelated products.</p></li><li><p>Different teams have completely independent release cycles.</p></li><li><p>Repository size becomes enormous.</p></li><li><p>CI/CD pipelines aren&#8217;t optimized.</p></li></ul><p>Without proper tooling, build times can also become slower.</p><div><hr></div><h2>Benefits of a Monorepo</h2><p>Some of the biggest advantages are:</p><ul><li><p>Easier refactoring : You can safely make changes across multiple projects.</p></li><li><p>Shared code : Utilities, DTOs, models, UI components, or libraries can live in one place.</p></li><li><p>Simpler dependency management : Everything uses compatible versions.</p></li><li><p>No wondering whether Repository A is using an older package than Repository B.</p></li><li><p>Easier onboarding : A new developer clones one repository and has access to the entire product.</p></li><li><p>Atomic commits : One pull request can include frontend, backend, tests, and documentation together.</p></li></ul><div><hr></div><h2>Challenges</h2><p>Of course, nothing is free.</p><p>A monorepo also comes with trade-offs:</p><ul><li><p>Larger Git repository</p></li><li><p>More complex CI/CD</p></li><li><p>Need for build caching</p></li><li><p>Good folder structure becomes critical</p></li><li><p>Permissions are harder if different teams shouldn&#8217;t see certain projects</p></li></ul><p>Modern tools like Nx, Turborepo, Bazel, Rush, and Pants help solve many of these challenges.</p><div><hr></div><h2>Which companies use Monorepos?</h2><p>Some well-known companies that use monorepos include:</p><ul><li><p>Google</p></li><li><p>Meta (Facebook)</p></li><li><p>Microsoft (many internal engineering teams)</p></li><li><p>Uber</p></li><li><p>Airbnb</p></li><li><p>X (formerly Twitter)</p></li><li><p>Dropbox</p></li></ul><p>Their repositories are obviously much larger than ours, but the idea is the same: keep related code together so teams can move faster.</p><div><hr></div><h2>Where did I use it?</h2><p>I use a monorepo for my SaaS project <a href="https://pakexam.com">PakExam</a>.</p><p>The repository currently contains:</p><ul><li><p>Website (Frontend)</p></li><li><p>Admin Panel</p></li><li><p>.NET Backend</p></li><li><p>Clean Architecture layers</p></li><li><p>Unit &amp; Integration Tests</p></li><li><p>Android App <em>(not live yet)</em></p></li><li><p>iOS App <em>(not live yet)</em></p></li></ul><p>Everything is developed from a single repository.</p><p>When I change the backend contract, I can immediately update the frontend, admin panel, and mobile applications without jumping between multiple repositories.</p><p>For a solo developer, this has made development much smoother.</p><p>And with the evolution of AI I can easily set instructions in the instructions file e.g. claude.md that whenever a change in backend API happens reflect it automatically wherever that API was being used</p><div><hr></div><h2>Should everyone use a Monorepo?</h2><p>No. It&#8217;s a tool, not a rule.</p><p>If you&#8217;re building several applications that share the same domain and are maintained by the same team, a monorepo is often an excellent choice.</p><p>If your products are completely independent and owned by different teams, multiple repositories may be the better option.</p><p>Choose the approach that reduces complexity for your team not the one that&#8217;s currently trending.</p><p>Have you ever used a monorepo, or do you prefer multiple repositories? </p><p>I&#8217;d love to hear your experience.</p><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p><span>Enhance your .NET skills by subscribing to my </span><strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 10,000+ subscribers</a><span> </span></strong><span>by sponsoring this newsletter</span></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Wolverine as Mediator in .NET]]></title><description><![CDATA[Read Time : 3 Mins]]></description><link>https://mwaseemzakir.substack.com/p/wolverine-as-mediator-in-net</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/wolverine-as-mediator-in-net</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 26 Apr 2025 04:14:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ymga!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ymga!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ymga!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!ymga!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!ymga!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!ymga!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ymga!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:67265,&quot;alt&quot;:&quot;&quot;,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.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_!ymga!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!ymga!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!ymga!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!ymga!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca9c2ac7-0416-4d0a-a838-8eae47dd2afc_1200x600.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 aria-hidden="true" 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><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>With the recent news that MediatR will become commercial in its upcoming versions, developers might be considering alternatives. I respect Jimmy Bogard's decision and wish him the best; however, if you are an individual, not a company, and are wondering how to navigate this change, today&#8217;s article will be helpful for you. </p><p>We will explore how to use this awesome NuGet package, free to use as of the date I am writing this article. </p><p>Wolverine is a <em>Next Generation .NET Mediator and Message Bus</em> available as a free nuget package on nuget package manager store.</p><p>Let&#8217;s start with a fresh .NET 8 Web API : </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!adil!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!adil!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png 424w, https://substackcdn.com/image/fetch/$s_!adil!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png 848w, https://substackcdn.com/image/fetch/$s_!adil!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png 1272w, https://substackcdn.com/image/fetch/$s_!adil!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!adil!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png" width="1456" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:130210,&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;:false,&quot;internalRedirect&quot;:&quot;https://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.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_!adil!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png 424w, https://substackcdn.com/image/fetch/$s_!adil!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png 848w, https://substackcdn.com/image/fetch/$s_!adil!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png 1272w, https://substackcdn.com/image/fetch/$s_!adil!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a785337-5684-40b4-8a42-08a60b73337a_1886x932.png 1456w" sizes="100vw"></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 aria-hidden="true" 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><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>Name your 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_!i372!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i372!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png 424w, https://substackcdn.com/image/fetch/$s_!i372!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png 848w, https://substackcdn.com/image/fetch/$s_!i372!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png 1272w, https://substackcdn.com/image/fetch/$s_!i372!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i372!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png" width="1456" height="719" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:719,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38456,&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;:false,&quot;internalRedirect&quot;:&quot;https://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.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_!i372!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png 424w, https://substackcdn.com/image/fetch/$s_!i372!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png 848w, https://substackcdn.com/image/fetch/$s_!i372!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png 1272w, https://substackcdn.com/image/fetch/$s_!i372!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9436dcd-eaaf-4e0b-9060-14958db37d70_1865x921.png 1456w" sizes="100vw"></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 aria-hidden="true" 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><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>I am using .NET 8 and choosing the controllers option :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BdgP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BdgP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png 424w, https://substackcdn.com/image/fetch/$s_!BdgP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png 848w, https://substackcdn.com/image/fetch/$s_!BdgP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png 1272w, https://substackcdn.com/image/fetch/$s_!BdgP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BdgP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png" width="1456" height="723" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:723,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38665,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.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_!BdgP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png 424w, https://substackcdn.com/image/fetch/$s_!BdgP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png 848w, https://substackcdn.com/image/fetch/$s_!BdgP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.png 1272w, https://substackcdn.com/image/fetch/$s_!BdgP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af73f43-7f01-4521-8ccd-480d8c0bba66_1877x932.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 aria-hidden="true" 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><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>Install the wolverine package : </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IJ_o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IJ_o!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png 424w, https://substackcdn.com/image/fetch/$s_!IJ_o!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png 848w, https://substackcdn.com/image/fetch/$s_!IJ_o!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png 1272w, https://substackcdn.com/image/fetch/$s_!IJ_o!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IJ_o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png" width="1456" height="779" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:779,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:141335,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.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_!IJ_o!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png 424w, https://substackcdn.com/image/fetch/$s_!IJ_o!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png 848w, https://substackcdn.com/image/fetch/$s_!IJ_o!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.png 1272w, https://substackcdn.com/image/fetch/$s_!IJ_o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ded83b3-f344-463d-84df-0f0652b610df_1902x1017.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 aria-hidden="true" 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><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>Register the dependency of this package :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uDPN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uDPN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png 424w, https://substackcdn.com/image/fetch/$s_!uDPN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png 848w, https://substackcdn.com/image/fetch/$s_!uDPN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png 1272w, https://substackcdn.com/image/fetch/$s_!uDPN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uDPN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png" width="925" height="752" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:752,&quot;width&quot;:925,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:79228,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.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_!uDPN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png 424w, https://substackcdn.com/image/fetch/$s_!uDPN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png 848w, https://substackcdn.com/image/fetch/$s_!uDPN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.png 1272w, https://substackcdn.com/image/fetch/$s_!uDPN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d37da6-6256-4e20-aa6b-36b89357cdbd_925x752.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 aria-hidden="true" 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><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>Let&#8217;s keep this super simple:</p><p>We want to send a &#8220;greeting&#8221; message and get back a friendly hello. In Wolverine, you don&#8217;t implement any special interfaces; everything happens by naming conventions.</p><p>First, define a plain request object that carries the user&#8217;s name. Then, create a public handler class whose name ends in <em>Handler (or Consumer)</em> and give it a single method that ends in <em>Handle (or Consume)</em>. Inside that method, you write your greeting logic.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!akVP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!akVP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png 424w, https://substackcdn.com/image/fetch/$s_!akVP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png 848w, https://substackcdn.com/image/fetch/$s_!akVP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png 1272w, https://substackcdn.com/image/fetch/$s_!akVP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!akVP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png" width="900" height="460" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:460,&quot;width&quot;:900,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48527,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.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_!akVP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png 424w, https://substackcdn.com/image/fetch/$s_!akVP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png 848w, https://substackcdn.com/image/fetch/$s_!akVP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.png 1272w, https://substackcdn.com/image/fetch/$s_!akVP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88260b2d-cacb-41e9-8979-777ab9f1a951_900x460.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 aria-hidden="true" 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><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>To send a message from our controller, we use the <code>IMessageBus</code> interface provided by the package. We inject <code>IMessageBus</code> into the controller&#8217;s constructor and then invoke it like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NCNW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NCNW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png 424w, https://substackcdn.com/image/fetch/$s_!NCNW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png 848w, https://substackcdn.com/image/fetch/$s_!NCNW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png 1272w, https://substackcdn.com/image/fetch/$s_!NCNW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NCNW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png" width="1041" height="472" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:472,&quot;width&quot;:1041,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56483,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.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_!NCNW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png 424w, https://substackcdn.com/image/fetch/$s_!NCNW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png 848w, https://substackcdn.com/image/fetch/$s_!NCNW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.png 1272w, https://substackcdn.com/image/fetch/$s_!NCNW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ca660dd-9acc-4d66-90ab-5a225023a1a8_1041x472.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 aria-hidden="true" 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><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>When you call <code>bus.InvokeAsync</code> With your request, Wolverine scans its known assemblies for any public class named <code>SomethingHandler</code> (or <code>SomethingConsumer</code>) and looks for a matching <code>Handle(...)</code> (or <code>Consume(...)</code>) method that takes your request type. It then invokes that method and returns the result no interfaces, no manual registrations, no extra boilerplate.</p><p>If your request returns a response, then we must specify it next to <code>InvokeAsync&lt;ResponseType&gt;. </code>Let&#8217;s try our 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_!WGyn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WGyn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png 424w, https://substackcdn.com/image/fetch/$s_!WGyn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png 848w, https://substackcdn.com/image/fetch/$s_!WGyn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png 1272w, https://substackcdn.com/image/fetch/$s_!WGyn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WGyn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png" width="1336" height="830" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:830,&quot;width&quot;:1336,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31096,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.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_!WGyn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png 424w, https://substackcdn.com/image/fetch/$s_!WGyn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png 848w, https://substackcdn.com/image/fetch/$s_!WGyn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.png 1272w, https://substackcdn.com/image/fetch/$s_!WGyn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f0dfae8-b224-4797-ab6d-c5be25915a92_1336x830.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 aria-hidden="true" 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><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>Ok but this is not enough we might need to add the fluent validation as well, so we can use this nugget come from same library : </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JYgq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JYgq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png 424w, https://substackcdn.com/image/fetch/$s_!JYgq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png 848w, https://substackcdn.com/image/fetch/$s_!JYgq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png 1272w, https://substackcdn.com/image/fetch/$s_!JYgq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JYgq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png" width="1396" height="848" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:848,&quot;width&quot;:1396,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81766,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.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_!JYgq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png 424w, https://substackcdn.com/image/fetch/$s_!JYgq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png 848w, https://substackcdn.com/image/fetch/$s_!JYgq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.png 1272w, https://substackcdn.com/image/fetch/$s_!JYgq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fa50a93-2bf3-46cc-9ff1-a48242441f64_1396x848.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 aria-hidden="true" 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><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>You don&#8217;t need to install <code>FluentValidation </code>separately this packages does it under the hood : </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!l2Pi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!l2Pi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png 424w, https://substackcdn.com/image/fetch/$s_!l2Pi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png 848w, https://substackcdn.com/image/fetch/$s_!l2Pi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png 1272w, https://substackcdn.com/image/fetch/$s_!l2Pi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!l2Pi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png" width="898" height="741" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:741,&quot;width&quot;:898,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:62529,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.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_!l2Pi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png 424w, https://substackcdn.com/image/fetch/$s_!l2Pi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png 848w, https://substackcdn.com/image/fetch/$s_!l2Pi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.png 1272w, https://substackcdn.com/image/fetch/$s_!l2Pi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff85c1f9a-c4a1-4234-b4a3-2f447b8affe5_898x741.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 aria-hidden="true" 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><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>Time to add some validation :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vgkX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vgkX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png 424w, https://substackcdn.com/image/fetch/$s_!vgkX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png 848w, https://substackcdn.com/image/fetch/$s_!vgkX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png 1272w, https://substackcdn.com/image/fetch/$s_!vgkX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vgkX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png" width="952" height="706" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:706,&quot;width&quot;:952,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82350,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.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_!vgkX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png 424w, https://substackcdn.com/image/fetch/$s_!vgkX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png 848w, https://substackcdn.com/image/fetch/$s_!vgkX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.png 1272w, https://substackcdn.com/image/fetch/$s_!vgkX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d024958-ba25-4360-bed6-dc2b4aed82de_952x706.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 aria-hidden="true" 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><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>Here we add and  modify our dependencies by adding FluentValidator as an option :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2Ax8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2Ax8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png 424w, https://substackcdn.com/image/fetch/$s_!2Ax8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png 848w, https://substackcdn.com/image/fetch/$s_!2Ax8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png 1272w, https://substackcdn.com/image/fetch/$s_!2Ax8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2Ax8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png" width="1120" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:675,&quot;width&quot;:1120,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:79790,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.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_!2Ax8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png 424w, https://substackcdn.com/image/fetch/$s_!2Ax8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png 848w, https://substackcdn.com/image/fetch/$s_!2Ax8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.png 1272w, https://substackcdn.com/image/fetch/$s_!2Ax8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5ac7b77-1af2-4ca6-b2b5-c9f10e7c6e25_1120x675.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 aria-hidden="true" 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><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>And here our validator comes into action : </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!acDd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!acDd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png 424w, https://substackcdn.com/image/fetch/$s_!acDd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png 848w, https://substackcdn.com/image/fetch/$s_!acDd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png 1272w, https://substackcdn.com/image/fetch/$s_!acDd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!acDd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png" width="1313" height="840" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:840,&quot;width&quot;:1313,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:122429,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.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_!acDd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png 424w, https://substackcdn.com/image/fetch/$s_!acDd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png 848w, https://substackcdn.com/image/fetch/$s_!acDd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.png 1272w, https://substackcdn.com/image/fetch/$s_!acDd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9a2ec62-52ef-4e61-a384-343281be7ce4_1313x840.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 aria-hidden="true" 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><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>That looks ugly, but we can do that some day later to make it look better.</p><p>Wolverine is packed with powerful features, durable outbox, scheduled delivery, sagas, and more but if you only need its in-process mediator capabilities, you can trim the extras in your <code>Program.cs</code>. By registering just the core mediator services, you avoid pulling in unnecessary modules and keep your application lean and efficient.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yr3E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yr3E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png 424w, https://substackcdn.com/image/fetch/$s_!yr3E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png 848w, https://substackcdn.com/image/fetch/$s_!yr3E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png 1272w, https://substackcdn.com/image/fetch/$s_!yr3E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yr3E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png" width="956" height="548" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:548,&quot;width&quot;:956,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64983,&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://mwaseemzakir.substack.com/i/162126274?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.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_!yr3E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png 424w, https://substackcdn.com/image/fetch/$s_!yr3E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png 848w, https://substackcdn.com/image/fetch/$s_!yr3E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.png 1272w, https://substackcdn.com/image/fetch/$s_!yr3E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F628b1559-cf18-498c-ae69-e981f524b3ec_956x548.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 aria-hidden="true" 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><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><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p>Enhance your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 10,000+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Introduction to Blazor ]]></title><description><![CDATA[Read Time : 3 Mins]]></description><link>https://mwaseemzakir.substack.com/p/introduction-to-blazor</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/introduction-to-blazor</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 16 Nov 2024 09:27:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!J8Sb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J8Sb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J8Sb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!J8Sb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!J8Sb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!J8Sb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J8Sb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71024,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J8Sb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!J8Sb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!J8Sb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!J8Sb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c4f766b-6f26-413f-98f2-00b278af0627_1200x600.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 aria-hidden="true" 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><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>Blazor is a modern web framework developed by Microsoft that allows developers to build interactive web applications using C# instead of JavaScript. </p><p>It leverages the power of .NET and provides a component-based architecture, making it easier to create rich user interfaces. </p><p>In this series about Blazor, we will dive into different aspects, and today we will discuss an introduction.</p><p></p><h3><strong>Key Features of Blazor</strong></h3><p>To fully grasp the blazor application, it's essential to understand its key features :</p><ol><li><p><strong>C# and .NET</strong>: Blazor enables developers to write client-side code in C#, allowing for a unified development experience across the client and server. You can share code and libraries between the server and the client.</p></li><li><p><strong>Component-Based Architecture</strong>: Blazor applications are built using reusable components. Each component encapsulates its rendering logic, state, and behavior, promoting modularity and reusability.</p></li><li><p><strong>Two Hosting Models</strong>:</p><ul><li><p><strong>Blazor Server</strong>: In this model, the application runs on the server, and UI updates are sent to the client over a SignalR connection. This allows for fast load times and reduced client resource usage.</p></li><li><p><strong>Blazor WebAssembly</strong>: This model runs the application directly in the browser using WebAssembly. It allows for offline capabilities and a more responsive user experience, as the application runs client-side.</p></li></ul></li><li><p><strong>Rich Interactivity</strong>: Blazor supports data binding, event handling, and dependency injection, enabling developers to create dynamic and interactive web applications.</p></li><li><p><strong>Integration with JavaScript</strong>: While Blazor allows you to write applications in C#, it also provides the ability to call JavaScript functions and use existing JavaScript libraries when needed.</p><ul><li><p><strong>Routing</strong>: Blazor includes built-in routing capabilities, allowing developers to define routes for different components and manage navigation within the application easily.</p></li><li><p><strong>Form Handling and Validation</strong>: Blazor provides robust support for forms, including data binding and validation, making it easier to create forms that are user-friendly and maintainable.</p></li></ul><p></p></li></ol><h3><strong>Components of Blazor</strong></h3><p>We can break down the blazor into the following components for better understanding:</p><ol><li><p><strong>Components</strong>: The building blocks of Blazor applications. A component is a self-contained unit of UI that can include HTML markup, C# code, and CSS styles.</p></li><li><p><strong>Razor Syntax</strong>: Blazor uses Razor syntax, which combines HTML and C# code. This allows for seamless integration of server-side logic with client-side rendering.</p></li><li><p><strong>Dependency Injection</strong>: Blazor supports dependency injection, allowing you to manage service lifetimes and inject services into components easily.</p></li></ol><p></p><h3><strong>Use Cases for Blazor</strong></h3><p>Here are the use cases for Blazor:</p><ol><li><p><strong>Single Page Applications (SPAs)</strong>: Blazor is well-suited for building SPAs that require rich interactivity and dynamic content updates.</p></li><li><p><strong>Enterprise Applications</strong>: With its component-based architecture and C# support, Blazor is ideal for developing complex enterprise applications that require maintainability and scalability.</p></li><li><p><strong>Progressive Web Apps (PWAs)</strong>: Blazor WebAssembly can be used to create PWAs that work offline and provide a native-like experience on the web.</p><p></p></li></ol><h3><strong>Summary</strong></h3><p>Blazor represents a significant shift in web development by allowing developers to use C# and .NET for building interactive web applications.</p><p>Its component-based architecture, rich interactivity, and support for both server-side and client-side hosting models make it a powerful choice for modern web development. </p><p>Whether you are building SPAs, enterprise applications, or PWAs, Blazor provides the tools and flexibility needed to create high-quality web applications.</p><div><hr></div><p>Tap the &#10084;&#65039; if you enjoyed this article&#8212;it motivates me to keep sharing more!</p><p>Have suggestions or thoughts to contribute?<br>Share them in the comments &#128172;. Your feedback is always valued and appreciated!</p><p>Share it with others who might benefit &#9851;&#65039;</p><div><hr></div><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p>Enhance your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 9,700+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Keywords to Remember in C#]]></title><description><![CDATA[Read Time : 5 Mins]]></description><link>https://mwaseemzakir.substack.com/p/keywords-to-remember-in-c</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/keywords-to-remember-in-c</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 09 Nov 2024 07:48:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HD7l!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HD7l!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HD7l!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!HD7l!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!HD7l!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!HD7l!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HD7l!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66341,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HD7l!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!HD7l!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!HD7l!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!HD7l!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cbc0e45-9f20-43db-9cb7-2275a4c9aac3_1200x600.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 aria-hidden="true" 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><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><h3>1/ Static</h3><p>The <code>static </code>keyword is used to declare members that belong to the type itself rather than to any specific instance. This means you can access static members without creating an instance of the class. Static members are shared across all instances of the class : </p><pre><code>public class Program
{
    public static void Main(string[] args)
    {
        double squareOfFour = MathUtilities.Square(4);
    }
}

public class MathUtilities
{
    public static double Square(double number)
    {
        return number * number;
    }
}</code></pre><h3>2/ Constant</h3><p>The <code>const </code>keyword is used to declare a constant field or local. Constants are immutable values that are known at compile time and do not change for the life of the program. They must be initialized at the time of declaration<code>.</code></p><pre><code>public class Circle
{
    public const double Pi = 3.14159;

    public double CalculateCircumference(double radius)
    {
        return 2 * Pi * radius;
    }
}</code></pre><h3>3/ Readonly</h3><p>The<code> readonly </code>keyword is used to declare a field that can only be assigned during its declaration or in the constructor of the same class. This is useful for fields that should not change after the object is constructed.</p><pre><code>public class Configuration
{
    public readonly string _configName;

    public Configuration(string name)
    {
        _configName = name;
    }
}</code></pre><h3>4/ Async and Await</h3><p>The <code>async </code>and <code>await </code>keywords are used to write asynchronous code. <code>async </code>is used to declare a method as asynchronous, and <code>await </code>is used to pause the execution of the method until the awaited task completes. This allows for non-blocking operations, improving the responsiveness of applications.</p><pre><code>public async Task&lt;string&gt; FetchDataAsync()
{
    await Task.Delay(1000);

    return "Data fetched";
}</code></pre><h3>5/ Yield</h3><p>The <code>yield </code>keyword is used in iterator methods to provide a value to the enumerator object and to signal the position in the code to resume execution when the next element is requested. It simplifies the implementation of iterators.</p><pre><code>public IEnumerable&lt;int&gt; GetNumbers()
{
    for (int i = 0; i &lt; 5; i++)
    {
        yield return i;
    }
}</code></pre><h3>6/ Ref</h3><p>The <code>ref</code> keyword is used to pass arguments by reference, allowing the method to modify the original variable:</p><pre><code>public class Program
{
    public static void Main(string[] args)
    {
        int number = 5;

        DoubleValue(ref number);

        Console.WriteLine($"{number}");
    }

    public static void DoubleValue(ref int number)
    {
        number *= 2;
    }
}
---
10</code></pre><h3>7/ Out</h3><p>Similar to <code>ref</code>, the <code>out</code> keyword is used to pass arguments by reference, but it requires the method to assign a value to the parameter.</p><pre><code>public class Program
{
    public static void Main(string[] args))
    {
        if (TryParseNumber("123", out int result))
        {
            Console.WriteLine($"Parsed number: {result}");
        }
        else
        {
            Console.WriteLine("Failed to parse number.");
        }
    }

    public static bool TryParseNumber(string input, out int result)
    {
        return int.TryParse(input, out result);
    }
}
---
Parsed number: 123</code></pre><h3>8/ Params</h3><p>The <code>params</code> keyword allows you to pass a variable number of arguments to a method:</p><pre><code>public class Program
{
    public static void Main((string[] args)
    {
        int sum = Sum(1, 2, 3, 4, 5);

        Console.WriteLine($"Sum: {sum}");
    }

    public static int Sum(params int[] numbers)
    {
        int sum = 0;

        foreach (int number in numbers)
        {
            sum += number;
        }
        return sum;
    }
}
---
Sum: 15</code></pre><h3>9/ Is and As</h3><p>The <code>is</code> keyword checks if an object is of a specific type.</p><p>The <code>as</code> keyword is used to perform safe type conversions, returning <code>null</code> if the conversion fails:</p><pre><code>public void CheckType(object obj)
{
    if (obj is string)
    {
        Console.WriteLine("It's a string!");
    }

    string str = obj as string;

    if (str != null)
    {
        Console.WriteLine("Conversion successful!");
    }
}</code></pre><h3>10/ Lock</h3><p>The <code>lock</code> keyword is used to ensure that a block of code runs exclusively in a multi-threaded environment.</p><pre><code>private readonly object _lock = new object();

public void ThreadSafeMethod()
{
    lock (_lock)
    {
        // Critical section
    }
}</code></pre><h3>11/ Base</h3><p><code>base</code> refers to the base class constructor or members:</p><pre><code>public class BaseClass
{
    public virtual void Display()
    {
        Console.WriteLine("Base class display");
    }
}

public class DerivedClass : BaseClass
{
    public override void Display()
    {
        base.Display();
        Console.WriteLine("Derived class display");
    }
}</code></pre><h3>12/ This</h3><p><code>this</code> Refers to the current instance of the class.</p><pre><code>public class Person
{
    private string name;

    public Person(string name)
    {
        this.name = name;
    }
}</code></pre><h3>13/ New</h3><p><code>new </code>keyword hides a member inherited from a base class or creates an object :</p><pre><code>public class BaseClass
{
    public void Display()
    {
        Console.WriteLine("Base class display");
    }
}

public class DerivedClass : BaseClass
{
    public new void Display()
    {
        Console.WriteLine("Derived class display");
    }
}</code></pre><h3>14/ Abstract</h3><p><code>abstract </code>defines a class or method that must be implemented by derived classes.</p><pre><code>public abstract class Shape
{
    public abstract double Area();
}

public class Circle : Shape
{
    private double radius;

    public Circle(double radius)
    {
        this.radius = radius;
    }

    public override double Area()
    {
        return Math.PI * radius * radius;
    }
}</code></pre><h3>15/ Sealed</h3><p><code>sealed </code>keyword prevents a class from being inherited.</p><pre><code>public sealed class FinalClass
{
    public void Display()
    {
        Console.WriteLine("This class cannot be inherited");
    }
}</code></pre><h3>16/ Override</h3><p><code>override </code>keyword overrides a virtual method in a base class :</p><pre><code>public class BaseClass
{
    public virtual void Display()
    {
        Console.WriteLine("Base class display");
    }
}

public class DerivedClass : BaseClass
{
    public override void Display()
    {
        Console.WriteLine("Derived class display");
    }
}</code></pre><h3>17/ Partial</h3><p>partial keyword splits a class, struct, or method definition across multiple files.</p><pre><code>// File1.cs
public partial class PartialClass
{
    public void Method1()
    {
        Console.WriteLine("Method1");
    }
}

// File2.cs
public partial class PartialClass
{
    public void Method2()
    {
        Console.WriteLine("Method2");
    }
}</code></pre><div><hr></div><p>Tap the &#10084;&#65039; if you enjoyed this article&#8212;it motivates me to keep sharing more!</p><p>Have suggestions or thoughts to contribute?<br>Share them in the comments &#128172;. Your feedback is always valued and appreciated!</p><p>Share it with others who might benefit &#9851;&#65039;</p><div><hr></div><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p>Enhance your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 9,700+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Setting Up OpenAI Chat in a .NET API]]></title><description><![CDATA[Read Time : 2 Mins]]></description><link>https://mwaseemzakir.substack.com/p/setting-up-openai-chat-in-a-net-api</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/setting-up-openai-chat-in-a-net-api</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 02 Nov 2024 06:14:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6DoE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6DoE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6DoE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!6DoE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!6DoE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!6DoE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6DoE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71666,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6DoE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!6DoE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!6DoE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!6DoE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F722f7866-a651-4cc7-8563-e31572eee1b6_1200x600.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 aria-hidden="true" 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><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>AI is everywhere! </p><p>Let&#8217;s walk through three simple steps to add it to your Web App or Web API : </p><h3>Get your API Key</h3><p> Retrieve your API key from the <a href="https://platform.openai.com/">OpenAI website</a>. Make sure you have already created the account.</p><p></p><h3><strong>Install the Required NuGet Package</strong></h3><p>Add the OpenAI NuGet package to your .NET API project : </p><pre><code>dotnet add package OpenAI</code></pre><p></p><h3><strong>Configure Your Code</strong></h3><p>Set up your code to initialize and use the OpenAI API, this is the sample code</p><pre><code> public async Task&lt;string&gt; Chat(string query)
 {
     ChatClient client = new(model: "gpt-4o", apiKey: API_KEY);

     ChatCompletion completion = await client.CompleteChatAsync(query);

     string response = completion.Content[0].Text;

     return response;
 }</code></pre><p>This code's purpose is to demonstrate how we can connect the Open AI, we will see in the next sections how we can improve this code.</p><p></p><h3>Available Methods and Properties</h3><p><code>ChatClient </code>comes from the <code>OpenAI.Chat </code>and the <code>ChatClient </code>constructor can take:</p><ol><li><p>Model and ApiKey</p></li><li><p>Model and ApiKeyCredential</p></li><li><p>Model, ApiKeyCredential and OpenAIClientOptions</p></li></ol><p>It has the following methods:</p><pre><code>Task&lt;ClientResult&lt;ChatCompletion&gt;&gt;CompleteChatAsync(
   IEnumerable&lt;ChatMessage&gt; messages, 
   ChatCompletionOptions options = null, 
   CancellationToken cancellationToken = default);</code></pre><pre><code>ClientResult&lt;ChatCompletion&gt; CompleteChat(
   IEnumerable&lt;ChatMessage&gt; messages, 
   ChatCompletionOptions options = null, 
   CancellationToken cancellationToken = default);</code></pre><pre><code>Task&lt;ClientResult&lt;ChatCompletion&gt;&gt; CompleteChatAsync(
params ChatMessage[] messages);

ClientResult&lt;ChatCompletion&gt; CompleteChat(params ChatMessage[] messages);</code></pre><p>Each method has its Async version as well we can use the one according to our need.</p><p>There is an additional asynchronous method called <code>CompleteChatStreaming</code>. Unlike <code>CompleteChatAsync</code>, this method streams the completion back token by token as it is generated.</p><p><code>ChatCompletion </code>contains <code>Id</code>, <code>Model</code>, <code>SystemFingerprint</code>, <code>Role</code>, <code>Content </code>, <code>ToolCalls </code>and <code>Usage</code> details.</p><p>Listing every detail isn&#8217;t feasible, but our response is found within the <code>Content</code> object. This object includes <code>Text</code>, <code>Refusal</code>, and additional properties related to images if the response contains one.</p><p></p><h3>Best Practices</h3><p>Let&#8217;s improve this code : </p><pre><code><code>public async Task&lt;string&gt; Chat(string query)
 {
     ChatClient client = new(model: "gpt-4o", apiKey: API_KEY);

     ChatCompletion completion = await client.CompleteChatAsync(query);

     string response = completion.Content[0].Text;

     return response;
 }</code></code></pre><p>1. Create an <code>Enum </code>to define the available models rather than passing the model as a string.</p><p>2. Retrieve the <code>API_Key</code> from app settings or a secure vault.</p><p>3. Implement a dedicated service for OpenAI features to ensure reusability.</p><p>4. Provide separate methods for asynchronous and synchronous calls.</p><p>5. Perform <code>NULL </code>checks to validate the query.</p><p>6. Use <code>try-catch </code>blocks to handle exceptions.</p><p>7. Refer to OpenAI's documentation to display detailed error messages for each error 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_!4a2V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4a2V!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png 424w, https://substackcdn.com/image/fetch/$s_!4a2V!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png 848w, https://substackcdn.com/image/fetch/$s_!4a2V!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png 1272w, https://substackcdn.com/image/fetch/$s_!4a2V!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4a2V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png" width="845" height="775" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:775,&quot;width&quot;:845,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81786,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4a2V!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png 424w, https://substackcdn.com/image/fetch/$s_!4a2V!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png 848w, https://substackcdn.com/image/fetch/$s_!4a2V!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.png 1272w, https://substackcdn.com/image/fetch/$s_!4a2V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b4e91cb-682a-4b85-ae73-9fc1f226621e_845x775.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 aria-hidden="true" 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><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><h3>Common Errors in Package Configuration</h3><p>While setting up the library you can get this error : </p><blockquote><p>The model <code>gpt-4o</code> does not exist or you do not have access to it.</p></blockquote><p>To resolve this you must have a five USS balance in your Open API account. </p><div><hr></div><p>Tap the &#10084;&#65039; if you enjoyed this article&#8212;it motivates me to keep sharing more!</p><p>Have suggestions or thoughts to contribute?<br>Share them in the comments &#128172;. Your feedback is always valued and appreciated!</p><p>Share it with others who might benefit &#9851;&#65039;</p><div><hr></div><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p>Enhance your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 9,700+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Data Annotations in C#]]></title><description><![CDATA[Read Time : 3 Min]]></description><link>https://mwaseemzakir.substack.com/p/data-annotations-in-c</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/data-annotations-in-c</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 26 Oct 2024 06:00:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!b9S1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3><strong>Effortlessly Schedule Your Content with BrandGhost</strong></h3><p>Are you a content creator or a small business looking to enhance your social media presence? With <a href="https://www.brandghost.ai/">BrandGhost</a>, you can effortlessly share your content across multiple platforms without the hassle of manual scheduling. Reclaim your time for what matters most and let <a href="https://www.brandghost.ai/">BrandGhost </a>effortlessly deliver your content to your audience!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.brandghost.ai/&quot;,&quot;text&quot;:&quot;START YOUR 30-DAY TRIAL&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.brandghost.ai/"><span>START YOUR 30-DAY TRIAL</span></a></p><div><hr></div><p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!b9S1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b9S1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!b9S1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!b9S1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!b9S1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b9S1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:62336,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!b9S1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!b9S1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!b9S1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!b9S1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F320d02ad-8b62-47d3-bd49-d05c17b54a59_1200x600.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 aria-hidden="true" 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><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>Data validation ensures that only correct and expected data reaches our databases, as user input can be unpredictable. We can use various validation methods, including Fluent Validation and Data Annotations to achieve this.</p><p>In this article, we&#8217;ll explore how to implement data annotations effectively in a web application.</p><p></p><h3>Applying Validation Using Data Annotations</h3><p>Data annotations allow us to apply validation by adding attributes to model properties. The <code>System.ComponentModel.DataAnnotations</code> namespace provides these attributes by default in .NET, with no additional setup needed. </p><p></p><h3>Common Validation Attributes</h3><p>Common validation attributes include:</p><ul><li><p>[Required]</p></li><li><p>[StringLength]</p></li><li><p>[Range]</p></li><li><p>[RegularExpression]</p></li><li><p>[EmailAddress]</p></li><li><p>[Compare]</p></li><li><p>[MinLength] </p></li><li><p>[MaxLength]</p></li><li><p>[AllowedValues]</p></li></ul><p>We'll explore each of these with code examples.</p><p></p><h3>Using Validation Attributes in C#</h3><h4>[Required]</h4><p>Ensures that a property is not null or empty : </p><pre><code>using System.ComponentModel.DataAnnotations;

namespace DataAnnotations.Models;

public sealed class User
{
    [Required]
    public string Name { get; set; }
    
    ... Other properties ...
}</code></pre><p>This field is now required, and if a value is not provided, an error message will be displayed like this.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mdIm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mdIm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png 424w, https://substackcdn.com/image/fetch/$s_!mdIm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png 848w, https://substackcdn.com/image/fetch/$s_!mdIm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png 1272w, https://substackcdn.com/image/fetch/$s_!mdIm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mdIm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png" width="546" height="107" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:107,&quot;width&quot;:546,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4618,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mdIm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png 424w, https://substackcdn.com/image/fetch/$s_!mdIm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png 848w, https://substackcdn.com/image/fetch/$s_!mdIm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png 1272w, https://substackcdn.com/image/fetch/$s_!mdIm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F951b56f4-9aa1-4b66-ad72-e1e9f781e613_546x107.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Validation Error</figcaption></figure></div><p>To customize the default error message, we can add the <code>ErrorMessage</code> attribute, like this : </p><pre><code>public sealed class User
{
    [Required(ErrorMessage = "Name is required.")]
    public string Name { get; set; }
    
    ... Other properties ...
}</code></pre><h4>[StringLength]</h4><p>Specifies the minimum and maximum length of a string property : </p><pre><code>public sealed class User
{
  [StringLength(maximumLength:100,
   MinimumLength=6,
   ErrorMessage="Password must be between 6 and 100 characters")]
  public string Password { get; set; }
    
    ... Other properties ...
}</code></pre><p><strong>[Range]</strong></p><p>Defines a numeric range for a property :</p><pre><code><code>public sealed class User
{
     [Range(18, 65, ErrorMessage = "Age must be between 18 and 65")]
     public int Age { get; set; }
   
    ... Other properties ...
}</code></code></pre><h4>[RegularExpression]</h4><p>Validates a property against a regular expression pattern :</p><pre><code><code>public sealed class User
{
     [RegularExpression(@"^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$",
     ErrorMessage = "Invalid email format.")]
     public string Email { get; set; }
   
    ... Other properties ...
}</code></code></pre><h4>[EmailAddress]</h4><p>Validate a property whether it is a valid email or not:</p><pre><code><code>public sealed class User
{
     [EmailAddress(ErrorMessage ="Email address is not valid")]
     public string Email { get; set; }
   
    ... Other properties ...
}</code></code></pre><h4>[Compare]</h4><p>It compares the value of one property with another and makes sure both are the same:</p><pre><code>public sealed class User
{
     [Required]
     [Compare("Password", 
     ErrorMessage = "Password and ConfirmPassword should be same")]
     public string ConfirmPassword { get; set; }

    [Required]
    public string Password { get; set; }
}</code></pre><p></p><h4>[MinLength] and [MaxLength]</h4><p>Another way to specify the minimum and maximum length of a string or array type property : </p><pre><code>public sealed class User
{
    [Required]
    [MinLength(4)]
    [MaxLength(20)]
    public string Name { get; set; }
    
    ... Other properties ...
}</code></pre><h4>[AllowedValues]</h4><p>Gets the list of values allowed by this attribute : </p><pre><code>public sealed class User
{
    [AllowedValues(UserTypes.Admin)]
    public UserTypes UserType { get; set; }
    
    ... Other properties ...
}

public enum UserTypes
{
    Admin,
    Customer
}</code></pre><p></p><h3>How validation is triggering</h3><p>If you're wondering how this validation is triggered, you'll need to add <code>asp-validation-for</code> it to your HTML(View in my case for MVC)</p><pre><code> &lt;div class="form-group"&gt;
     &lt;label asp-for="Name" class="control-label"&gt;&lt;/label&gt;
     &lt;input asp-for="Name" class="form-control" /&gt;
     &lt;span asp-validation-for="Name" class="text-danger"&gt;&lt;/span&gt;
 &lt;/div&gt;</code></pre><p></p><h3>Additional Purposes of Data Annotations </h3><p>Data annotations can also serve additional purposes beyond validation, such as:</p><h4><strong>[Display]</strong></h4><p>Specifies how data, including name, description, and order, is displayed in the UI.</p><h4><strong>[DisplayFormat]</strong></h4><p>Specifies the format for displaying data, such as date and currency formats.</p><h4><strong>[DataType]</strong></h4><p>Provides additional type information, such as DataType.Date, DataType.Time, DataType.Currency, etc.</p><h4><strong>[Key]</strong></h4><p>Specifies that a property is the primary key of an entity.</p><h4><strong>[ForeignKey]</strong></h4><p>Specifies a foreign key relationship between entities.</p><div><hr></div><p>Tap the &#10084;&#65039; if you enjoyed this article&#8212;it motivates me to keep sharing more!</p><p>Have suggestions or thoughts to contribute?<br>Share them in the comments &#128172;. Your feedback is always valued and appreciated!</p><p>Share it with others who might benefit &#9851;&#65039;</p><div><hr></div><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p>Enhance your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 9,000+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol><p></p>]]></content:encoded></item><item><title><![CDATA[Built-in Middleware in ASP.NET Core]]></title><description><![CDATA[Read Time : 3 Mins]]></description><link>https://mwaseemzakir.substack.com/p/built-in-middleware-in-aspnet-core</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/built-in-middleware-in-aspnet-core</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 19 Oct 2024 06:01:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Hutc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Hutc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Hutc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!Hutc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!Hutc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!Hutc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Hutc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:69113,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Hutc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!Hutc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!Hutc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!Hutc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3770f5a3-f448-4d56-ade3-8dfa2abf5452_1200x600.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 aria-hidden="true" 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><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>Middlewares are used to add additional logic before and after HTTP requests. </p><p>We can create custom middleware, and there is a wide range of built-in middleware available that we can utilize:</p><p></p><blockquote><p>Simply adding a middleware code (as show in the article) does not guarantee that its intended purpose will be fully achieved. Additional services, configurations, and logic may be required to meet specific application needs. </p><p>This article aims to explain the purpose of commonly seen middleware in the <code>Program.cs</code> file, along with a few additional middleware components.</p></blockquote><p></p><h3><strong>1. Static Files Middleware</strong></h3><p><strong>Purpose:</strong> Serves static files such as HTML, CSS, JavaScript, and images directly from the file system.</p><pre><code>app.UseStaticFiles();</code></pre><p><strong>Details:</strong> This middleware is typically placed early in the pipeline to efficiently serve static content without further processing.</p><p></p><h3><strong>2. Routing Middleware</strong></h3><p><strong>Purpose:</strong> Matches incoming HTTP requests to endpoints defined in the application.</p><pre><code>app.UseRouting();</code></pre><p><strong>Details:</strong> Routing middleware is essential for directing requests to the appropriate controllers or endpoints. It should be placed before any middleware that depends on routing information, such as authorization.</p><p></p><h3><strong>3. Authentication Middleware</strong></h3><p><strong>Purpose:</strong> Handles user authentication, verifying user credentials, and establishing user identity.</p><pre><code>app.UseAuthentication();</code></pre><p><strong>Details:</strong> This middleware should be placed before authorization middleware to ensure that users are authenticated before access checks are performed.</p><p></p><h3><strong>4. Authorization Middleware</strong></h3><p><strong>Purpose:</strong> Enforces access control policies, ensuring that authenticated users have the necessary permissions to access resources.</p><pre><code>app.UseAuthorization();</code></pre><p><strong>Details:</strong> Authorization middleware should be placed after authentication middleware to ensure that only authenticated users are authorized.</p><p></p><h3><strong>5. CORS Middleware</strong></h3><p><strong>Purpose:</strong> Configures Cross-Origin Resource Sharing (CORS) policies to allow or restrict resources requested from another domain.</p><pre><code>app.UseCors("AllowAllPolicy");</code></pre><p><strong>Details:</strong> CORS middleware is crucial for enabling secure cross-origin requests, especially in API applications.</p><p></p><h3><strong>6. Session Middleware</strong></h3><p><strong>Purpose:</strong> Manages user sessions, allowing data to be stored and retrieved across multiple requests.</p><pre><code>app.UseSession();</code></pre><p><strong>Details:</strong> Session middleware requires session services to be configured in services.</p><p></p><h3><strong>7. Response Compression Middleware</strong></h3><p><strong>Purpose:</strong> Compresses HTTP responses to reduce bandwidth usage and improve load times.</p><pre><code>app.UseResponseCompression();</code></pre><p><strong>Details:</strong> This middleware is beneficial for optimizing performance, especially for large responses.</p><p></p><h3><strong>8. Exception Handling Middleware</strong></h3><p><strong>Purpose:</strong> Provides a centralized mechanism for handling exceptions and generating error responses.</p><pre><code>app.UseExceptionHandler("/Home/Error"); //Web App

app.UseExceptionHandler(); //API</code></pre><p><strong>Details:</strong> Exception-handling middleware should be placed early in the pipeline to catch exceptions from subsequent middleware.</p><p></p><h3><strong>9. HTTPS Redirection Middleware</strong></h3><p><strong>Purpose:</strong> Redirects HTTP requests to HTTPS, ensuring secure communication.</p><pre><code>app.UseHttpsRedirection();</code></pre><p><strong>Details:</strong> This middleware is essential for enforcing HTTPS in production environments.</p><p></p><h3><strong>10. HSTS Middleware</strong></h3><p><strong>Purpose:</strong> Enforces HTTP Strict Transport Security (HSTS) headers, instructing browsers to only access the site over HTTPS.</p><pre><code>app.UseHsts();</code></pre><p><strong>Details:</strong> HSTS middleware should be used in conjunction with HTTPS redirection for enhanced security.</p><p></p><h3>11. Run Middleware </h3><p><strong>Purpose:</strong> Defines a terminal middleware delegate that handles HTTP requests and generates responses directly, without passing control to subsequent middleware.</p><pre><code>app.Run();</code></pre><p><strong>Details:</strong> app.Run() is used to set up a terminal middleware in the ASP.NET Core request processing pipeline. It is typically placed at the end in <code>Program.cs</code> file and is responsible for producing the final response. </p><div><hr></div><p>Tap the &#10084;&#65039; if you enjoyed this article&#8212;it motivates me to keep sharing more!</p><p>Have suggestions or thoughts to contribute?<br>Share them in the comments &#128172;. Your feedback is always valued and appreciated!</p><p>Share it with others who might benefit &#9851;&#65039;</p><div><hr></div><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p>Enhance your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 9,000+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol><p></p>]]></content:encoded></item><item><title><![CDATA[Enable Cross-Origin Requests (CORS) in ASP.NET Core]]></title><description><![CDATA[Read Time : 4 Mins]]></description><link>https://mwaseemzakir.substack.com/p/enable-cross-origin-requests-cors</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/enable-cross-origin-requests-cors</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 12 Oct 2024 06:31:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!i8hI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i8hI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i8hI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!i8hI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!i8hI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!i8hI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i8hI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78004,&quot;alt&quot;:&quot;Enable CORS in .NET&quot;,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Enable CORS in .NET" title="Enable CORS in .NET" srcset="https://substackcdn.com/image/fetch/$s_!i8hI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!i8hI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!i8hI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!i8hI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fceef3ca1-7223-453d-93db-40860bf2c9b4_1200x600.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 aria-hidden="true" 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><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>Cross-Origin Resource Sharing (CORS) is essential when building .NET web applications to allow them to handle requests from different origins. </p><p>By default, web applications are governed by the <em>same-origin policy</em>, which restricts web browsers from requesting a different origin for security reasons.</p><p></p><h3>What is the Same-Origin Policy?</h3><p>The <em>same-origin policy</em> ensures that a web application can only interact with resources from the same origin (i.e., the same domain). For instance, consider the following URLs:</p><ul><li><p><strong>URL 1</strong>: <code>https://www.facebook.com/GetFriends</code></p></li><li><p><strong>URL 2</strong>: <code>https://www.facebook.com/GetPosts</code></p></li></ul><p>Both URLs share the same origin because they have the same protocol (<code>https</code>), domain (<code>www.facebook.com</code>).</p><p>Consider these two URLs:</p><ul><li><p><strong>URL 1</strong>: <code>https://www.facebook.com/GetFriends</code></p></li><li><p><strong>URL 2</strong>: <code>https://www.twitter.com/GetFollowers</code></p></li></ul><p>These URLs have different origins since they belong to different domains (<code>facebook.com</code> vs <code>twitter.com</code>). In such cases, a browser will block requests to the second origin unless CORS is enabled.</p><p></p><h3>Why Do We Need CORS?</h3><p>If you have a .NET Core Web API providing data and multiple applications from different origins need to consume this API, the <em>same-origin policy</em> would block their requests. To facilitate cross-origin communication, we enable CORS in our web application. This allows it to:</p><ol><li><p>Handle requests from different origins.</p></li><li><p>Bypass the browser's same-origin policy, ensuring that valid requests from external applications are accepted.</p></li></ol><p>Without CORS, web browsers cannot make cross-origin requests, and it will give an error similar to this : </p><blockquote><p>Acces to XMLHttpRequest at '{WebAPI_URL}' from origin '{ClientApp_URL}' has been blocked by CORS policy: No 'Acces-Control-Allow-Origin' header is present on the requested resource.</p></blockquote><p></p><h3>Example Scenario</h3><p>Imagine you have a .NET Core Web API that handles user data for multiple client applications. These client applications might be hosted on different domains&#8212;let's say:</p><ul><li><p>https://app1.example.com</p></li><li><p>https://app2.example.com</p></li><li><p>https://mobile.example.com</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4e_k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4e_k!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png 424w, https://substackcdn.com/image/fetch/$s_!4e_k!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png 848w, https://substackcdn.com/image/fetch/$s_!4e_k!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png 1272w, https://substackcdn.com/image/fetch/$s_!4e_k!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4e_k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png" width="1456" height="94" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:94,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:24722,&quot;alt&quot;:&quot;CORS Error&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="CORS Error" title="CORS Error" srcset="https://substackcdn.com/image/fetch/$s_!4e_k!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png 424w, https://substackcdn.com/image/fetch/$s_!4e_k!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png 848w, https://substackcdn.com/image/fetch/$s_!4e_k!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png 1272w, https://substackcdn.com/image/fetch/$s_!4e_k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd7e514-bae9-4655-958e-34e0b16ce8db_1617x104.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">CORS Error</figcaption></figure></div><p>In the provided scenario, I attempted hit the <code>GetBooks</code> method of a Web API from an Angular project without enabling CORS. As expected, it was blocked due to the <em>same-origin policy</em>.</p><p>To resolve this, I would need to enable CORS on the Web API, allowing requests from the Angular application&#8217;s origin. Let&#8217;s do it.</p><blockquote><p>Fun Fact: A common misconception with CORS is that the request/call doesn't reach the server at all. However, the request <em>does</em> actually reach the server-side controller endpoint, but the browser blocks the response from being accessed by the front end due to the CORS policy.</p></blockquote><p></p><h3>How to Enable CORS in .NET</h3><p>To properly handle CORS in a .NET web application, we need to define a list of allowed domains (origins) that can make requests to our API. A more flexible and maintainable approach is to store these allowed origins in the application's settings file, such as <code>appsettings.json</code>, and load them dynamically at runtime.</p><p><strong>STEP-I:</strong> Store Allowed Domains in<strong> </strong><code>appsettings.json</code>: Instead of hardcoding the allowed domains in the code, we can define them in the configuration file:</p><pre><code>{
  "AllowedOrigins": [
    "https://app1.example.com",
    "https://app2.example.com",
    "https://mobile.example.com"
  ]
}</code></pre><p><strong>STEP-II:</strong> I've retrieved the array of allowed origins from the app settings and used them to register CORS services : </p><pre><code>string allowedSpecificOrigins = "AllowSpecificOrigins";

WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);

string[]? allowedOrigins = builder
                 .Configuration
                 .GetSection("AllowedOrigins")
                 .Get&lt;string[]&gt;();

builder.Services.AddCors(options =&gt;
{
    options.AddPolicy(allowedSpecificOrigins,
        policy =&gt;
           {
               policy.WithOrigins(allowedOrigins)
                     .AllowAnyMethod()
                     .AllowAnyHeader();
            });
});

builder.Services.AddControllers();</code></pre><p><strong>STEP-III:</strong> Enable CORS middleware :</p><pre><code>app.UseCors(allowedSpecificOrigins);</code></pre><p>Available Methods:</p><ul><li><p><strong>AllowAnyOrigin</strong>: Permits requests from all origins without restriction.</p></li><li><p><strong>WithOrigins</strong>: Restricts requests to specific origins. You can pass one or more origins as arguments to this method.</p></li><li><p><strong>AllowAnyHeader</strong>: Permits requests with any headers.</p></li><li><p><strong>WithHeaders</strong>: Restricts requests to specific headers. You can pass one or more headers as arguments to this method.</p></li><li><p><strong>AllowAnyMethod</strong>: Permits requests with any HTTP method (e.g., GET, POST, PUT, DELETE).</p></li><li><p><strong>WithMethods</strong>: Restricts requests to specific HTTP methods. You can pass one or more methods as arguments to this method.</p></li></ul><h3><strong>Important things to keep in mind about CORS</strong></h3><p>&#10004;&#65039; CORS is not a security feature. CORS is a W3C standard that allows a server to relax the same-origin policy.</p><p>&#10004;&#65039; An API isn&#8217;t safer by allowing CORS.</p><p>&#10004;&#65039; The order in which you register the CORS middleware is crucial. Ensure that the CORS registration is placed in the correct position within the middleware pipeline. Below is the default middleware order, including CORS : </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!I2my!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!I2my!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png 424w, https://substackcdn.com/image/fetch/$s_!I2my!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png 848w, https://substackcdn.com/image/fetch/$s_!I2my!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png 1272w, https://substackcdn.com/image/fetch/$s_!I2my!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!I2my!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png" width="918" height="522" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:522,&quot;width&quot;:918,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38554,&quot;alt&quot;:&quot;Middlewares default order&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Middlewares default order" title="Middlewares default order" srcset="https://substackcdn.com/image/fetch/$s_!I2my!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png 424w, https://substackcdn.com/image/fetch/$s_!I2my!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png 848w, https://substackcdn.com/image/fetch/$s_!I2my!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.png 1272w, https://substackcdn.com/image/fetch/$s_!I2my!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff27ae47-06e7-4312-a9ed-162c26725fd8_918x522.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 aria-hidden="true" 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><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><figcaption class="image-caption">Middlewares default order</figcaption></figure></div><div><hr></div><p>Tap the &#10084;&#65039; if you enjoyed this article&#8212;it motivates me to keep sharing more!</p><p>Have suggestions or thoughts to contribute?<br>Share them in the comments &#128172;. Your feedback is always valued and appreciated!</p><p>Share it with others who might benefit &#9851;&#65039;</p><div><hr></div><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p>Enhance your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 9,000+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Top 18 Essential .NET Libraries for Developers]]></title><description><![CDATA[Read Time : 4 Mins]]></description><link>https://mwaseemzakir.substack.com/p/top-15-essential-net-libraries-for</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/top-15-essential-net-libraries-for</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 05 Oct 2024 06:01:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_4tu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_4tu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_4tu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!_4tu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!_4tu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!_4tu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_4tu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/caf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:73881,&quot;alt&quot;:&quot;Top Essential Libraries for .NET Developers&quot;,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Top Essential Libraries for .NET Developers" title="Top Essential Libraries for .NET Developers" srcset="https://substackcdn.com/image/fetch/$s_!_4tu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!_4tu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!_4tu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!_4tu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaf2284f-7a49-494f-a58e-94a7d7cc43d2_1200x600.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 aria-hidden="true" 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><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><h3><strong>Implementation Links for Each .NET Library</strong></h3><p>Find the implementation links for each library under the relevant headings.</p><p></p><h3><strong>1- Mediator</strong></h3><p>Use Case: Implements the CQRS pattern for separating read and write operations.</p><p>Pros: Simplifies code structure, and promotes single responsibility.</p><p>According to Microsoft Docs:</p><blockquote><p>CQRS stands for Command and Query Responsibility Segregation, a pattern that separates read and update operations for a data store.</p></blockquote><p>Learn more about design patterns in simple explanations e.g. <a href="https://mwaseemzakir.com/newsletters/episode50/">Facade Pattern</a>, <a href="https://mwaseemzakir.com/newsletters/episode51/">Null Object Pattern</a>, <a href="https://mwaseemzakir.com/newsletters/episode47/">Adapter Design Pattern</a>, <a href="https://mwaseemzakir.com/newsletters/episode46/">Decorator Pattern</a>, <a href="https://mwaseemzakir.com/newsletters/episode64/">Service Locator Pattern, </a>and <a href="https://mwaseemzakir.com/newsletters/episode21/">Repository Design Patterns</a></p><p></p><h3><strong>2- <a href="https://mwaseemzakir.com/newsletters/episode23/">Dapper</a></strong></h3><p>Use Case: A micro-ORM for mapping database results to objects.</p><p>Pros: Lightweight, easy to use.</p><p>Trivia: You can use Entity Framework when you need a full-featured ORM with advanced capabilities like change tracking and migrations. Dapper is my first choice when I have to write lengthy SQL queries with a lot of tables and complex joins.</p><p></p><h3><strong>3- <a href="https://mwaseemzakir.com/newsletters/episode60/">Serilog</a></strong></h3><p>Use Case: Logging frameworks for simple and structured logging (Serilog)</p><p>Pros: Flexible, supports various sinks (40+ sinks)</p><p>Trivia: If you require a more traditional logging framework then use NLog.</p><p></p><h3><strong>4- <a href="https://mwaseemzakir.com/newsletters/episode13/">Bogus</a></strong></h3><p>Use Case: Generates fake data for testing and development.</p><p>Pros: Easy to use, customizable data generation.</p><p>Trivia: For simple applications, I would prefer to create my custom bogus implementation instead of going for a library. </p><p></p><h3><strong>5- <a href="https://mwaseemzakir.com/newsletters/episode58/">Fluent Validation</a></strong></h3><p>Use Case: Validates models in a fluent interface style.</p><p>Pros: Clear syntax, easy to maintain.</p><p>Trivia: Use Data Annotations for simpler validation needs where built-in attributes suffice.</p><p></p><h3><strong>6- <a href="https://mwaseemzakir.com/newsletters/episode32/">Refit</a></strong></h3><p>Use Case<strong>:</strong> Simplifying HTTP API calls in .NET applications.</p><p>Pros<strong>: </strong>Reduces boilerplate code for making HTTP requests.</p><p>Trivia: Refit under the hood use HttpClient</p><p></p><h3><strong>7- <a href="https://mwaseemzakir.com/newsletters/episode19/">Health Checks</a></strong></h3><p>Use Case<strong>:</strong> Monitoring the health of applications and services </p><p>Pros<strong>: </strong>Provides a standardized way to observe the status of services, it can be integrated with monitoring tools for alerts. Additionally, it helps ensure application reliability and availability.</p><p></p><h3><strong>8,9- Hangfire and <a href="https://mwaseemzakir.com/newsletters/episode3/">Quartz</a></strong></h3><p>Use Case: Background job processing.</p><p>Pros: Reliable, easy to schedule jobs, and helpful in complex scenarios.</p><p>Trivia: Use Background Services for simple use cases. </p><p></p><h3><strong>10- <a href="https://mwaseemzakir.com/newsletters/episode17/">Noda Time</a></strong></h3><p>Use Case: Advanced date and time handling.</p><p>Pros: Comprehensive, handles time zones well.</p><p>Trivia: Use <code>System.DateTime</code> for simpler applications where advanced date-time handling is not required. BTW Jon Skeet is the owner of the Noda Time library</p><p></p><h3><strong>11- <a href="https://mwaseemzakir.com/newsletters/episode11/">Autofac</a></strong></h3><p>Use Case: Dependency Injection container.</p><p>Pros: Flexible, supports advanced scenarios.</p><p>Trivia: Use Microsoft.Extensions.DependencyInjection for simpler dependency injection needs.</p><p></p><h3><strong>12- </strong>MiniProfiler</h3><p>Use Case: Speed profiling of applications.</p><p>Pros: Easy to integrate, provides detailed insights.</p><p>Trivia: Use Application Insights if you need more comprehensive monitoring.</p><p></p><h3><strong>13- <a href="https://mwaseemzakir.com/newsletters/episode6/">Mapster</a></strong></h3><p>Use Case: Object-to-object mapping.</p><p>Pros: Reduces boilerplate code, and improves maintainability.</p><p>Trivia: There is a big debate about mappers and custom mapping, and I have covered a few questions : </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MSd2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MSd2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png 424w, https://substackcdn.com/image/fetch/$s_!MSd2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png 848w, https://substackcdn.com/image/fetch/$s_!MSd2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png 1272w, https://substackcdn.com/image/fetch/$s_!MSd2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MSd2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png" width="922" height="742" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e017f350-d84b-43a5-b34d-d59398590fab_922x742.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:742,&quot;width&quot;:922,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:70480,&quot;alt&quot;:&quot;FAQs about using Mapping Libraries or Custom Implementation&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="FAQs about using Mapping Libraries or Custom Implementation" title="FAQs about using Mapping Libraries or Custom Implementation" srcset="https://substackcdn.com/image/fetch/$s_!MSd2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png 424w, https://substackcdn.com/image/fetch/$s_!MSd2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png 848w, https://substackcdn.com/image/fetch/$s_!MSd2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.png 1272w, https://substackcdn.com/image/fetch/$s_!MSd2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe017f350-d84b-43a5-b34d-d59398590fab_922x742.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 aria-hidden="true" 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><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><figcaption class="image-caption">FAQs</figcaption></figure></div><p></p><h3><strong>14- NSubstitute</strong></h3><p>Use Case: Mocking framework for unit testing.</p><p>Pros: Easy to set up, supports complex scenarios.</p><p>Trivia: I was a big fan of Moq but I quit because they were quietly collecting users' data</p><p></p><h3><strong>15- <a href="https://mwaseemzakir.com/newsletters/episode9/">System.Text.Json</a></strong></h3><p>Use Case: JSON serialization/deserialization.</p><p>Pros: Mature, widely used, flexible, better performant</p><p>Trivia: NewtonSoft.Json is also a good option but I prefer System.Text.Json due to better performant.</p><p></p><h3><strong>16- BenchmarkDotNet</strong></h3><p>Use Case: Speed measurement of code.</p><p>Pros: Accurate, easy to use.</p><p>Trivia: It can be overkill for simple checks.</p><p></p><h3><strong>17- Swagger</strong></h3><p>Use Case: API documentation and testing.</p><p>Pros: Interactive documentation, easy to use.</p><p>Trivia: Use Postman Desktop Application, for API testing and documentation without the need for integrated solutions.</p><p></p><h3><strong>18- SignalR</strong></h3><p>Use Case: Real-time web functionality (e.g., chat applications).</p><p>Pros: Simplifies real-time communication, and supports various transports.</p><p>Use Web Sockets, if you need a more low-level approach to real-time communication without the overhead of a framework.</p><p></p><div><hr></div><p>Tap the &#10084;&#65039; if you enjoyed this article&#8212;it motivates me to keep sharing more!</p><p>Have suggestions or thoughts to contribute?<br>Share them in the comments &#128172;. Your feedback is always valued and appreciated!</p><p>Share it with others who might benefit &#9851;&#65039;</p><div><hr></div><h3><strong>There are 2 ways I can help you:</strong></h3><ol><li><p>Enhance your .NET skills by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 9,000+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol>]]></content:encoded></item><item><title><![CDATA[EP 66 : Clean Code Tips for .NET Developers - Part I]]></title><description><![CDATA[Read Time : 6 Mins]]></description><link>https://mwaseemzakir.substack.com/p/ep-66-clean-code-tips-for-net-developers</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/ep-66-clean-code-tips-for-net-developers</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 28 Sep 2024 06:03:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MkI9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://mwaseemzakir.com/sponsorship/">Sponsor this newsletter</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MkI9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MkI9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!MkI9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!MkI9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!MkI9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MkI9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86727,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MkI9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!MkI9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!MkI9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!MkI9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a411ea7-d6d9-4b90-b634-2c7099910252_1200x600.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 aria-hidden="true" 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><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>Writing clean code is crucial for development. If writing code is art then writing clean code would be a masterpiece.</p><p>Check out the first post of the clean code tips series :</p><h3>1- Give meaningful names</h3><p>Using meaningful names makes code self-explanatory, reducing the need for additional comments.</p><p>Bad Example :</p><pre><code>public class Emp
{
    public string n;
    public int a;

    public void d()
    {
       Console.WriteLine($"Name: {name}, Age: {age}");
    }
}</code></pre><p>Good Example : </p><pre><code><code>public class Employee
{
    public string name;
    public int age;

    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
    }
}</code></code></pre><h3>2- Avoid returning null</h3><p>Returning null can lead to <code>NullReferenceException </code>errors when the caller attempts to access properties or methods on the returned object. It can also make the code harder to read and maintain, as the caller must always check for null before using the returned value.</p><p>Suppose we have this piece of code : </p><pre><code>public class UserService
{
    public User? GetUserById(int id)
    {
        if (id &lt;= 0)
        {
            return null; 
        }
        .... Rest logic of method ...
        return user;
    }
}</code></pre><p>We can improve it with this, instead of returning null we can throw an exception and then handle it separately : </p><pre><code><code>public sealed class UserService
{
    public User GetUserById(int id)
    {
        if (id &lt;= 0)
        {
            // throws an exception instead of returning null
            throw new ArgumentException("Invalid user Id"); 
        }
        .... Rest logic of method ...
        return user;
    }
}</code></code></pre><h3>3- Keep class | method size small</h3><p>A class should have one reason to change. </p><p>Smaller classes are easier to manage, test, and maintain.</p><p> At some points, this asks us to follow the <em><strong>Single Responsibility Principle</strong></em>.</p><p>Suppose we have this example in which we have a lot of code, with different concerns :</p><pre><code><code>public sealed class OrderProcessing
{
    public void ProcessOrder(Order order)
    {
        if (!ValidateOrder(order))
        {
            throw new ArgumentException("Invalid order");
        }

        ChargePayment(order);
      
        UpdateInventory(order);

        SendConfirmationEmail(order);

        LogOrderDetails(order);
    }

    private bool ValidateOrder(Order order) {  }
    private void ChargePayment(Order order) {  }
    private void UpdateInventory(Order order) {  }
    private void SendConfirmationEmail(Order order) { }
    private void LogOrderDetails(Order order) { }
}</code></code></pre><p>This could be improved like this where we have separate classes for each concern : </p><pre><code><code>public sealed class OrderValidator
{
    public bool Validate(Order order){}
}

public sealed class PaymentProcessor
{
    public void Charge(Order order){}
}

public sealed class InventoryManager
{
    public void Update(Order order){}
}

public sealed class EmailService
{
    public void SendConfirmation(Order order){}
}

public sealed class OrderProcessing
{
    private readonly OrderValidator _validator;
    private readonly PaymentProcessor _paymentProcessor;
    private readonly InventoryManager _inventoryManager;
    private readonly EmailService _emailService;

    public OrderProcessing(
      OrderValidator validator, 
      PaymentProcessor paymentProcessor,
      InventoryManager inventoryManager,
      EmailService emailService)
    {
        _validator = validator;
        _paymentProcessor = paymentProcessor;
        _inventoryManager = inventoryManager;
        _emailService = emailService;
    }

    public void ProcessOrder(Order order)
    {
        if (!_validator.Validate(order))
        {
            throw new ArgumentException("Invalid order");
        }

        _paymentProcessor.Charge(order);
        _inventoryManager.Update(order);
        _emailService.SendConfirmation(order);
    }
}</code></code></pre><p>Although there is no hard-code rule about how many lines of code a method should contain, 20-40 lines are enough for a method. </p><h3>4- Don&#8217;t reinvent the wheel</h3><p>Reinventing the wheel can lead to unnecessary duplication of effort, wasting valuable time and resources that could be better spent on developing unique features. </p><p>What exactly is reinventing the wheel, it means when we already have code for some issue and we start writing manually code to solve that cause then we are reinventing the wheel : </p><pre><code>public sealed class CustomLogger
{
    public void Log(string message)
    {
        using (var writer = new StreamWriter("log.txt", true))
        {
            writer.WriteLine($"{DateTime.Now}: {message}");
        }
    }
}</code></pre><p>We can achieve that by just injecting the ILogger interface, considering logging is already configured : </p><pre><code><code>public sealed class Application
{
    private readonly ILogger _logger;

    public Application(ILogger logger)
    {
        _logger = logger;
    }
}</code></code></pre><p>Read about how to implement logging in .NET : </p><ul><li><p><a href="https://mwaseemzakir.com/newsletters/episode1/">Logging with NLog</a></p></li><li><p><a href="https://mwaseemzakir.com/newsletters/episode60/">Logging with Serilog</a></p></li></ul><h3>5-  Use appropriate tools/IDE</h3><p>Using the right tools or IDE enhances productivity by providing features like syntax highlighting, code completion, and error checking, which help catch mistakes early.</p><p>Keep reading the new update of Visual Studio, and invest time in learning about existing features of tools as well. </p><p>Additionally, these are two practices that I follow in my .NET projects, and I would recommend that you do the same: </p><h4>1- Enabling Editor Config File</h4><p>Enabling the editor config file in .NET projects to apply some sort of rules on all code </p><pre><code>root = true

# C# files
[*.cs]

#### Core EditorConfig Options ####

# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4

#### .NET Coding Conventions ####

# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true</code></pre><h4>2- Adding the props file</h4><p>This props file is used to configure common settings and apply them across all projects  for example: </p><pre><code>&lt;Project&gt;
  &lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net7.0&lt;/TargetFramework&gt;
    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
    &lt;Nullable&gt;enable&lt;/Nullable&gt;
    &lt;TreatWarningsAsErrors&gt;true&lt;/TreatWarningsAsErrors&gt;
  &lt;/PropertyGroup&gt;
&lt;/Project&gt;</code></pre><h3><strong>Whenever you&#8217;re ready, there are 2 ways I can help you:</strong></h3><ol><li><p>If you want to boost your .NET skills and do some code in action you can do that by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.com/sponsorship/">Promote yourself to 11,000+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol>]]></content:encoded></item><item><title><![CDATA[EP 65 : Primary Constructor in C#]]></title><description><![CDATA[Read Time : 1 Mins]]></description><link>https://mwaseemzakir.substack.com/p/ep-65-primary-constructor-in-c</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/ep-65-primary-constructor-in-c</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 06 Jul 2024 09:20:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Ig5d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.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_!Ig5d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ig5d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!Ig5d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!Ig5d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!Ig5d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ig5d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:85062,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ig5d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!Ig5d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!Ig5d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!Ig5d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0dd04bd-d0ca-4ac3-aeb0-48000ff9cfd2_1200x600.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 aria-hidden="true" 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><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><ul><li><p>What is the primary constructor? </p></li><li><p>Understanding with example</p></li><li><p>Benefits of using them</p></li></ul><h3>What is the primary constructor? </h3><p>Primary constructors were introduced in .NET 8 and C# 12, a cleaner way of constructor initialization. We can use this syntax where we have a lot of constructor injections.</p><h3>Understanding the Primary Constructor with Code</h3><p>Let&#8217;s understand it with a  simple example first before diving in depth. The older way of constructor injection :</p><pre><code>public sealed class NewsletterService
{
    private readonly INewsletterRepository _newsletterRepository;

    public NewsletterService(INewsletterRepository newsletterRepository)
    {
        _newsletterRepository = newsletterRepository;
    }
}</code></pre><p>This is how we can achieve the same with primary constructors:</p><pre><code>internal class NewsletterService(INewsletterRepository newsletterRepository)
{
    public void Subscribe(string email)
    {
        newsletterRepository.Add(email);
    }
}</code></pre><h3>Pro(s) :</h3><p>The following are the benefits of using primary constructors :</p><ol><li><p>It removes a lot of boilerplate code</p></li><li><p>Simplifies the syntax</p></li><li><p>Much more readability</p></li></ol><h3><strong>Whenever you&#8217;re ready, there are 2 ways I can help you:</strong></h3><ol><li><p>If you want to boost your .NET skills and do some code in action you can do that by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.substack.com/p/waseems-net-newsletter-sponsorship">Promote yourself to 10400+ subscribers</a> </strong>by sponsoring this newsletter</p></li><li><p><strong><a href="https://www.patreon.com/mwaseemzakir">Patreon Community:</a></strong> Join and gain access to the 200+ articles I have published so far in one place. <strong><a href="https://www.patreon.com/mwaseemzakir">Join here</a></strong></p></li></ol>]]></content:encoded></item><item><title><![CDATA[EP 64 : Service Locator Pattern in .NET ]]></title><description><![CDATA[Read Time : 3 Mins]]></description><link>https://mwaseemzakir.substack.com/p/ep-64-service-locator-pattern-in</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/ep-64-service-locator-pattern-in</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sat, 22 Jun 2024 06:56:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!b8s3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong><a href="https://mwaseemzakir.substack.com/p/waseems-net-newsletter-sponsorship">Sponsor this newsletter</a></strong></p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!b8s3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b8s3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!b8s3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!b8s3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!b8s3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b8s3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png" width="1200" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86100,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!b8s3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png 424w, https://substackcdn.com/image/fetch/$s_!b8s3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png 848w, https://substackcdn.com/image/fetch/$s_!b8s3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.png 1272w, https://substackcdn.com/image/fetch/$s_!b8s3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0f4a31-5413-4845-a93f-1adb30b7e4f3_1200x600.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 aria-hidden="true" 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><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><ul><li><p>What is the service locator pattern?</p></li><li><p>When should we use it?</p></li><li><p>How to use it in .NET</p></li><li><p>Is using it a bad practice?</p></li><li><p>Disadvantages</p></li></ul><div><hr></div><h3>What is the service locator pattern?</h3><p>We can inject any interface into the constructor and use it in dependency injection. The default dependency injection container in .NET will handle the instantiation and disposal of objects.</p><p>If, for some reason, we are unable to inject those interfaces and we need to resolve those dependencies, we can utilize the service locator pattern as a solution.</p><p>In this pattern, we inject our service provider into the class and we can retrieve the N number of services using that single provider.</p><p></p><h3>When should we use this pattern?</h3><p>There are situations where this pattern could be helpful, but it should be used wisely in the following cases:</p><p>1. When dealing with services scope mismatch issues</p><p>2. When working with an old code base that requires extensive changes for update</p><p>3. Sometimes when creating generic classes or maybe when types are not clear until runtime</p><p></p><h3>How to use it in .NET?</h3><p>We need to inject IServiceProvider in our desired class and then retrieve the required service by calling <code>GetRequiredService</code> method</p><pre><code>public class UsersService
{
    private readonly IServiceProvider _serviceProvider;
&#9;
    public UsersService(IServiceProvider ServiceProvider) 
    {
        _serviceProvider = ServiceProvider;
    }
}</code></pre><p>Then we can use it at the controller level or method level :</p><pre><code>public class UsersService
{
    private readonly IServiceProvider _serviceProvider;
    
    private readonly IUserRolesRepository _userRolesRepository;
&#9;
    public UsersService(IServiceProvider ServiceProvider) 
    {
        _serviceProvider = ServiceProvider;
&#9;&#9;
       _userRolesRepository =_serviceProvider
             .GetRequiredService&lt;IUserRolesRepository&gt;();
    }
&#9;
    public void Method1()
    {
        var userRepository = _serviceProvider
                .GetRequiredService&lt;IUserRepository&gt;();
    }&#9;
}</code></pre><p>This is a very simple example of how we can use <code>IServiceProvider,</code> let&#8217;s move one step next.</p><h3>Using a global implementation for a service provider</h3><p>Instead of putting IServiceProvider everywhere, we can use a more universal approach by using a single class responsible for the services like this :</p><pre><code>public class CachedServiceProvider 
{
&#9;protected IServiceProvider ServiceProvider { get; }
&#9;
&#9;protected ConcurrentDictionary&lt;Type, Lazy&lt;object&gt;&gt; Services { get; }

&#9;public CachedServiceProvider(IServiceProvider serviceProvider)
&#9;{
&#9;   ServiceProvider = serviceProvider;
&#9;&#9;
&#9;   Services = new ConcurrentDictionary&lt;Type, Lazy&lt;object&gt;&gt;();
&#9;&#9;
&#9;   Services
             .TryAdd(typeof(IServiceProvider), new Lazy&lt;object&gt;(() =&gt;                ServiceProvider));
&#9;}

&#9;public virtual object GetService(Type serviceType)
&#9;{
&#9;&#9;return Services.GetOrAdd(
&#9;&#9;  serviceType,
&#9;&#9;&#9;_ =&gt; new Lazy&lt;object&gt;(() =&gt;        ServiceProvider.GetService(serviceType)!)
&#9;&#9;).Value;
&#9;}
}</code></pre><p>Using this approach our single class is responsible for providing services, and then we are using a dictionary to cache the services so the same service is not created again and again.</p><p>By using a concurrent dictionary we are making sure that it can accessed by multiple threads concurrently.</p><h3>Is it bad practice to use this pattern?</h3><p>Going with direct dependency injection should always be the first option because that is much cleaner, manageable, and easy to unit test.</p><p>But if that is not possible then we can opt for this pattern, like every pattern this also has some cons. But we can not say that using this is bad because it depends on the situation and use case.</p><p></p><h3>Disadvantages of this pattern?</h3><p>These are the disadvantages of using this pattern:</p><ol><li><p>The biggest issue is testing, mocking and testing can become more complex because the service locator is often a singleton or a global instance.</p></li><li><p>It can make dependencies less obvious, as they are not directly visible in the code where services are used.</p></li><li><p>Over-reliance on the service locator can lead to poor design choices, where everything is fetched from the locator, leading to an anemic domain model</p></li></ol><p></p><h3><strong>Whenever you&#8217;re ready, there are 2 ways I can help you:</strong></h3><ol><li><p>If you want to boost your .NET skills and learn more about .NET you can do that by subscribing to my <strong><a href="https://www.youtube.com/channel/UCgKGlfkNAmIBprbI35C6SpQ?sub_confirmation=1">YouTube Channel</a></strong></p></li><li><p><strong><a href="https://mwaseemzakir.substack.com/p/waseems-net-newsletter-sponsorship">Promote yourself to 10400+ subscribers</a> </strong>by sponsoring this newsletter</p></li></ol><blockquote><h3><em>&#8220;To praise the sun is to praise your own eyes.&#8221; - Rumi</em></h3></blockquote>]]></content:encoded></item><item><title><![CDATA[EP 61 : Custom Type Handling with Dapper in .NET]]></title><description><![CDATA[Read Time : 2 Mins]]></description><link>https://mwaseemzakir.substack.com/p/ep-61-custom-type-handling-with-dapper</link><guid isPermaLink="false">https://mwaseemzakir.substack.com/p/ep-61-custom-type-handling-with-dapper</guid><dc:creator><![CDATA[Muhammad Waseem]]></dc:creator><pubDate>Sun, 16 Jun 2024 06:22:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!A6uN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong><a href="https://mwaseemzakir.substack.com/p/waseems-net-newsletter-sponsorship">Sponsor this newsletter</a></strong></p><div><hr></div><p>Dapper is an opensource performant ORM (Object Relational Mapping) library used extensively in the .NET world</p><p>If you want to learn how we can use it in .NET, read this article :</p><p><a href="https://mwaseemzakir.substack.com/p/ep-23-how-to-use-dapper-in-net-60">How to use Dapper in .NET 6.0 for CRUD Operations</a></p><ul><li><p>Adding an entity for the demo</p></li><li><p>Introducing value type</p></li><li><p>Handling conversion in configuration</p></li><li><p>Querying data with Dapper</p></li><li><p>Create type handler</p></li></ul><div><hr></div><h3>Basic User Entity</h3><p>We can take an example of a basic user entity, which contains a couple of properties :</p><pre><code><code>public sealed class User
{
    public Guid Id { get; private set; }

    public string Name { get; private set; } = string.Empty;

    public string Email { get; private set; }

    public string PasswordSalt { get; private set; } = string.Empty;

    public string PasswordHash { get; private set; } = string.Empty;
}
</code></code></pre><h3>Introducing Value Types</h3><p>After a couple of time, we have decided to introduce value types in our project for example in the current situation we could add an Email value type like this :</p><pre><code><code>public sealed record Email(string Value)
{
    public static Email Empty =&gt; new(string.Empty);
}</code></code></pre><p>We could introduce multiple value types and a proper type for each type on the same pattern. So after this change, our User class would look like this:</p><pre><code><code>public sealed class User
{
    public Guid Id { get; private set; }

    public string Name { get; private set; } = string.Empty;

    public Email Email { get; private set; }

    public string PasswordSalt { get; private set; } = string.Empty;

    public string PasswordHash { get; private set; } = string.Empty;
}</code></code></pre><h3>Handling Conversion in Configuration</h3><p>We need to add configuration so that type can be handled automatically in the configuration class of our entity like this :</p><pre><code><code>internal class UserConfiguration : IEntityTypeConfiguration&lt;User&gt;
{
    public void Configure(EntityTypeBuilder&lt;User&gt; builder)
    {
        builder.Property(user =&gt; user.Email)
            .HasMaxLength(400)
            .HasConversion(email =&gt; email.Value, value =&gt; new Email(value))
            .IsRequired();
    }
}</code></code></pre><h3>Querying data with Dapper</h3><p>Suppose this is our example where we are trying to retrieve a user using Dapper.</p><p>Let&#8217;s ignore the discussion that I could have used EF Core, let&#8217;s suppose this query is much more complex and I am displaying this for the sake of how we can solve complex types of problems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!A6uN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!A6uN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png 424w, https://substackcdn.com/image/fetch/$s_!A6uN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png 848w, https://substackcdn.com/image/fetch/$s_!A6uN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png 1272w, https://substackcdn.com/image/fetch/$s_!A6uN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!A6uN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png" width="1456" height="973" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:973,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:227244,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!A6uN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png 424w, https://substackcdn.com/image/fetch/$s_!A6uN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png 848w, https://substackcdn.com/image/fetch/$s_!A6uN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.png 1272w, https://substackcdn.com/image/fetch/$s_!A6uN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01ab6138-4144-41f3-8bb9-eee968b15b7a_2078x1388.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 aria-hidden="true" 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><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>This code will throw an error right something similar to this :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B2Th!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B2Th!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png 424w, https://substackcdn.com/image/fetch/$s_!B2Th!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png 848w, https://substackcdn.com/image/fetch/$s_!B2Th!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png 1272w, https://substackcdn.com/image/fetch/$s_!B2Th!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B2Th!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png" width="1456" height="227" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:227,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:57407,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!B2Th!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png 424w, https://substackcdn.com/image/fetch/$s_!B2Th!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png 848w, https://substackcdn.com/image/fetch/$s_!B2Th!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png 1272w, https://substackcdn.com/image/fetch/$s_!B2Th!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3b448-62b0-438d-bcd8-7fe49e6a87c4_1864x290.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Error is fine because the database Email type is a string, not the Email type we defined. This is where the type handler comes to the rescue.</p><h3>How to create a type handler in .NET?</h3><p>Type handlers handle complex types and parse them as per needs.</p><p>We can utilize <code>SqlMapper.TypeHandler&lt;T&gt; </code>from dapper for such a purpose.</p><p>Let&#8217;s create our Email type handler to resolve our issue :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xMuT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xMuT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png 424w, https://substackcdn.com/image/fetch/$s_!xMuT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png 848w, https://substackcdn.com/image/fetch/$s_!xMuT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png 1272w, https://substackcdn.com/image/fetch/$s_!xMuT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xMuT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png" width="1456" height="448" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:448,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:117333,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!xMuT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png 424w, https://substackcdn.com/image/fetch/$s_!xMuT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png 848w, https://substackcdn.com/image/fetch/$s_!xMuT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.png 1272w, https://substackcdn.com/image/fetch/$s_!xMuT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae72580f-bbbe-469e-8855-868afc73a5e4_2078x640.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 aria-hidden="true" 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><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>This will take care of the conversion for us, you can add a debugger to make sure, it works.</p><p>The last thing is to register this type of handler with DI :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!powc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!powc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png 424w, https://substackcdn.com/image/fetch/$s_!powc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png 848w, https://substackcdn.com/image/fetch/$s_!powc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png 1272w, https://substackcdn.com/image/fetch/$s_!powc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!powc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png" width="1456" height="265" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:265,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:72976,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!powc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png 424w, https://substackcdn.com/image/fetch/$s_!powc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png 848w, https://substackcdn.com/image/fetch/$s_!powc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png 1272w, https://substackcdn.com/image/fetch/$s_!powc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8c26e65-f02f-4a26-bbf9-40a6625d1b1e_2078x378.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h2><strong>Whenever you&#8217;re ready, there are 2 ways I can help you:</strong></h2><ol><li><p><strong><a href="https://mwaseemzakir.substack.com/p/waseems-net-newsletter-sponsorship">Promote yourself to 10400+ subscribers</a> </strong>by sponsoring this newsletter</p></li><li><p><strong><a href="https://www.patreon.com/mwaseemzakir">Patreon Community:</a></strong> Read 200+ articles I have published so far in one place.</p></li></ol>]]></content:encoded></item></channel></rss>