<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mo</title>
    <description>The latest articles on DEV Community by Mo (@ipazooki).</description>
    <link>https://dev.to/ipazooki</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1127050%2F4677b6e9-5072-42f2-83fd-050e3c322fc4.JPG</url>
      <title>DEV Community: Mo</title>
      <link>https://dev.to/ipazooki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ipazooki"/>
    <language>en</language>
    <item>
      <title>8 Filler Phrases That Quietly Weaken Your CV (And the Stronger Lines to Replace Them)</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Tue, 01 Sep 2026 09:27:59 +0000</pubDate>
      <link>https://dev.to/ipazooki/8-filler-phrases-that-quietly-weaken-your-cv-and-the-stronger-lines-to-replace-them-35bi</link>
      <guid>https://dev.to/ipazooki/8-filler-phrases-that-quietly-weaken-your-cv-and-the-stronger-lines-to-replace-them-35bi</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Most CVs are not weak because the person is weak. They are weak because filler phrases hide real work behind vague words. Below are eight common phrases, why each one costs you visibility, and a stronger line to use instead. At the end, I show how we built this thinking into CVSet, the CV scoring and tailoring tool I design.&lt;/p&gt;




&lt;p&gt;I have read a lot of CVs while building CVSet. The pattern is almost always the same: a strong engineer writes "responsible for maintaining internal systems" when what they actually did was rebuild a payment integration that six teams depend on.&lt;/p&gt;

&lt;p&gt;The facts were there. The wording buried them.&lt;/p&gt;

&lt;p&gt;That gap is the whole reason CVSet exists. Not to invent a better candidate — to make the real one visible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why filler creeps in
&lt;/h2&gt;

&lt;p&gt;Filler is not laziness. It comes from three habits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Job-description language.&lt;/strong&gt; You copy the phrasing from the advert you originally applied to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modesty.&lt;/strong&gt; "Helped with" feels safer than "led", especially if the work was shared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed.&lt;/strong&gt; You update your CV at 11 pm the night before a deadline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is not to exaggerate. It is to say the same thing with the specifics still attached.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 8 phrases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. "Responsible for…"
&lt;/h3&gt;

&lt;p&gt;This describes a job title, not a contribution. Anyone in that seat was responsible for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Responsible for the company's CI/CD pipeline.

✅ Rebuilt the CI/CD pipeline, cutting deployment time by 10%.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do not have a number, use scope instead: &lt;em&gt;"Rebuilt the CI/CD pipeline used by four engineering teams."&lt;/em&gt; Scope is still evidence.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. "Worked on…"
&lt;/h3&gt;

&lt;p&gt;"Worked on" hides your role completely. Did you design it, build it, review it, or attend the meetings?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Worked on a microservices migration.

✅ Migrated three monolith modules to independent .NET services.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick the verb that matches what you actually did. &lt;strong&gt;Built, designed, migrated, tested, documented&lt;/strong&gt; — each one tells a different story, and all of them beat "worked on".&lt;/p&gt;




&lt;h3&gt;
  
  
  3. "Helped with…"
&lt;/h3&gt;

&lt;p&gt;Shared work is normal. But "helped" makes you sound optional.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Helped with the authentication rewrite.

✅ Built the token refresh and session handling for the authentication rewrite.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are not claiming the whole project. You are naming your part of it. That is more honest, not less.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. "Team player"
&lt;/h3&gt;

&lt;p&gt;Every candidate says this. It is a claim with no evidence behind it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Strong team player with excellent collaboration skills.

✅ Ran weekly code reviews for a team of six and onboarded two junior developers.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second line proves collaboration without using the word.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. "Results-driven" / "Results-oriented"
&lt;/h3&gt;

&lt;p&gt;If you have results, show them. If you do not, the adjective will not cover the gap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Results-driven engineer focused on delivery.

✅ Reduced average API response time from 800ms to 210ms.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule: &lt;strong&gt;replace the adjective with the result it is describing.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  6. "Excellent communication skills"
&lt;/h3&gt;

&lt;p&gt;A recruiter cannot verify this. They can verify what you communicated, and to whom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Excellent written and verbal communication skills.

✅ Wrote the API documentation used by three external integration partners.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Audience plus artefact beats adjective every time.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. "Passionate about technology"
&lt;/h3&gt;

&lt;p&gt;Passion is not a qualification, and this line takes space near the top of your CV where space is expensive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Passionate about clean code and emerging technologies.

✅ Maintain an open-source .NET library with 400+ downloads.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your passion produced something, name the something. If it did not, cut the line.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. "Various" / "Multiple" / "A range of"
&lt;/h3&gt;

&lt;p&gt;These words exist to avoid listing. But the list is the valuable part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Experience with various cloud technologies.

✅ Experience with Azure App Service, Azure Functions and Azure SQL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one also matters for &lt;strong&gt;ATS keyword matching&lt;/strong&gt;. A scanner looking for "Terraform" will never match "various infrastructure tools".&lt;/p&gt;




&lt;h2&gt;
  
  
  The rule underneath all eight
&lt;/h2&gt;

&lt;p&gt;Every rewrite above follows the same principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Same facts, better visibility.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing was invented. No new skill appeared. No number was guessed. The work was always there — the wording just stopped hiding it.&lt;/p&gt;

&lt;p&gt;This is the constraint we designed CVSet around, and it is non-negotiable in the product. The AI never adds a skill, a tool, or an achievement that is not already in your CV.&lt;/p&gt;




&lt;h2&gt;
  
  
  How CVSet applies this
&lt;/h2&gt;

&lt;p&gt;Here is the flow, and what each step is actually for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Analyse
&lt;/h3&gt;

&lt;p&gt;Your CV is scored across nine dimensions, including &lt;strong&gt;Metrics &amp;amp; Impact&lt;/strong&gt;, &lt;strong&gt;Clarity &amp;amp; Brevity&lt;/strong&gt;, &lt;strong&gt;ATS Keywords&lt;/strong&gt; and &lt;strong&gt;Expertise Presentation&lt;/strong&gt;. Filler phrases usually show up as a low Metrics &amp;amp; Impact score, because vague verbs leave nothing to measure.&lt;/p&gt;

&lt;p&gt;You also see the CV through four perspectives: Hiring Manager, Recruiter, Human Eye, and Algorithm. The same sentence can read fine to a human and be invisible to a parser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F746l0z2idf6gwgyraar5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F746l0z2idf6gwgyraar5.png" alt="Recruiter's First Impression" width="800" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That split view is the honest version of the filler problem. The left column is what your wording already proves. The right column is what it does not — and neither side is a guess, both come from your own CV against that specific job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Optimise
&lt;/h3&gt;

&lt;p&gt;Analysis becomes suggestions. Each one names the problem and the next step, so you are never told "improve this" without being told how.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Tailor
&lt;/h3&gt;

&lt;p&gt;Here you paste a job description, and the CV is rewritten against it across five dimensions: &lt;strong&gt;Skills, Keywords, Language, Prioritisation, Quality&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Crucially, you accept or reject every change individually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5zwipo0b71nvituqlpq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5zwipo0b71nvituqlpq.png" alt="Changes &amp;amp; Explanations panel showing the Personal Statement with CURRENT and SUGGESTED versions, and the Accept / Keep as is / Alternative buttons." width="800" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the mechanism in that screenshot: the original text stays visible next to the suggestion, each change shows the score impact, and "Keep as is" is a first-class option. Prioritisation changes order only — no text is touched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxikpb4y0v4zckirt4m7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxikpb4y0v4zckirt4m7e.png" alt="Changes &amp;amp; Explanations in collapsed state, showing all five dimensions marked ALL REVIEWED." width="800" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — Skill Gap Analysis
&lt;/h3&gt;

&lt;p&gt;This is the part I care most about. When the job asks for something your CV genuinely does not contain, we do &lt;strong&gt;not&lt;/strong&gt; write it in.&lt;/p&gt;

&lt;p&gt;Instead, it goes to your To-Learn List, with a reason and a concrete way to close it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fheg72yi75rbepkw89p02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fheg72yi75rbepkw89p02.png" alt="Skill Gap Analysis page showing Pair programming (Critical), Identity and authentication systems (Important) and Terraform (Important), each with " width="800" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A gap is not a failure. It is the next thing to build.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we got wrong first
&lt;/h2&gt;

&lt;p&gt;Two honest lessons from building this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overlapping dimensions destroyed trust.&lt;/strong&gt; In an early version, both Metrics and Completeness flagged the same missing number. Users saw one problem reported twice and assumed the scoring was padded. We fixed it with a single-owner rule: each dimension owns specific properties and has a deny-list. If a dimension does not own it, it does not mention it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent changes feel worse than bad changes.&lt;/strong&gt; Anything the tool does without asking reads as untrustworthy, even when the change is good. That is why nothing is applied until you accept it, and why we surface the original text next to every suggestion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score ceilings had to be honest.&lt;/strong&gt; Not every CV can reach 100 on every dimension. Showing a ceiling you cannot reach is just a different kind of filler.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try this in ten minutes
&lt;/h2&gt;

&lt;p&gt;You do not need a tool to start. Open your CV and do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search for the eight phrases above.&lt;/li&gt;
&lt;li&gt;For each hit, ask: &lt;em&gt;what did I actually do here?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Rewrite using a specific verb plus a number or a scope.&lt;/li&gt;
&lt;li&gt;If you cannot find either, ask whether the line earns its space.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. No new experience required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filler is a visibility problem, not a truth problem.&lt;/strong&gt; Your work is already good enough; the wording is the bottleneck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace adjectives with the evidence they describe.&lt;/strong&gt; "Results-driven" becomes the actual result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use scope when you do not have a number.&lt;/strong&gt; Team size, system count and user count are all real evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never invent to fill a gap.&lt;/strong&gt; A missing skill belongs on your learning list, not in your experience section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vague words break ATS matching.&lt;/strong&gt; "Various cloud tools" matches nothing a parser is searching for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same facts, better visibility&lt;/strong&gt; is the only safe rewrite rule.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;Which filler phrase is still hiding in your CV right now? I would guess "responsible for" — it survives longer than the others because it sounds formal.&lt;/p&gt;

&lt;p&gt;Next post, I want to go deeper on the ATS side: what a parser actually reads, and why formatting choices like columns and photos change the outcome more than people expect.&lt;/p&gt;

&lt;p&gt;You can try the scoring at &lt;a href="https://cvset.io" rel="noopener noreferrer"&gt;cvset.io&lt;/a&gt;.&lt;/p&gt;




</description>
      <category>career</category>
      <category>ai</category>
      <category>jobsearch</category>
      <category>cv</category>
    </item>
    <item>
      <title>What an ATS Actually Does With Your CV (And How to Stay Visible)</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Tue, 01 Sep 2026 09:12:30 +0000</pubDate>
      <link>https://dev.to/ipazooki/what-an-ats-actually-does-with-your-cv-and-how-to-stay-visible-1c6e</link>
      <guid>https://dev.to/ipazooki/what-an-ats-actually-does-with-your-cv-and-how-to-stay-visible-1c6e</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — An Applicant Tracking System does three things: it &lt;strong&gt;parses&lt;/strong&gt; your CV into structured data, &lt;strong&gt;ranks&lt;/strong&gt; it against the job description, and &lt;strong&gt;filters&lt;/strong&gt; what a recruiter sees first. Most CVs lose points at the parsing stage, before anyone reads a word. Fix the structure, keep the facts, and you stay visible. This post explains each stage, then shows how we built &lt;a href="https://cvset.io" rel="noopener noreferrer"&gt;CVSet&lt;/a&gt; around it.&lt;/p&gt;




&lt;p&gt;Most job seekers picture the ATS as a robot with an opinion. It reads your CV, decides you are not good enough, and deletes you.&lt;/p&gt;

&lt;p&gt;That is not what happens. The ATS is closer to a filing cabinet with a search bar. It takes your document, tries to turn it into database rows, and hands the recruiter a searchable list. The damage happens when it cannot read your file properly, because a field it cannot fill is a field nobody can search.&lt;/p&gt;

&lt;p&gt;I have spent the last year building a CV scoring product, and this stage broke more CVs than any writing problem we saw.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 1: Parsing — the machine reads your file first
&lt;/h2&gt;

&lt;p&gt;Before anything is scored, your PDF or DOCX is converted into text, then split into fields.&lt;/p&gt;

&lt;p&gt;A parser is trying to fill a shape like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"experience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"start_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"end_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bullets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"education"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every empty field is a search you will not appear in. If &lt;code&gt;title&lt;/code&gt; is blank, you do not show up when a recruiter filters for "Product Designer".&lt;/p&gt;

&lt;p&gt;Here is the part people miss. Text extraction follows the &lt;strong&gt;reading order stored in the file&lt;/strong&gt;, not the visual order you see on screen. A two-column CV often comes out like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# What you see (two columns, side by side):
#   LEFT: Skills, Languages     RIGHT: Experience
#
# What the extractor returns:

Skills
Figma, Next.js, Azure
Languages
English, Persian
Experience
Senior Product Designer
CVSet · 2024 – Present
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Readable enough for a human. But the parser now has to guess where the sidebar ends and the job history begins. Tables, text boxes and headers make this worse, because their content is often stored in a separate layer of the document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually breaks parsing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contact details inside the page header or footer&lt;/li&gt;
&lt;li&gt;Job titles and dates inside table cells&lt;/li&gt;
&lt;li&gt;Icons used instead of labels (a phone glyph is not the word "Phone")&lt;/li&gt;
&lt;li&gt;Dates written as images, or as ranges the parser cannot pattern-match&lt;/li&gt;
&lt;li&gt;Scanned or exported-as-image PDFs with no text layer at all&lt;/li&gt;
&lt;li&gt;Creative section names like "My Journey" instead of "Experience"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are writing problems. They are file problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 2: Ranking — keyword matching, not judgement
&lt;/h2&gt;

&lt;p&gt;Once the fields are filled, the system compares your CV to the job description. The logic is simpler than most people expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified version of what most matching logic does.
# No intelligence here — just presence, position and frequency.
&lt;/span&gt;
&lt;span class="n"&gt;jd_terms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;react&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;typescript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accessibility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;design systems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cv_text&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extracted_cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;jd_terms&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cv_text&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;score&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jd_terms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# 0.5
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jd_terms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# {'accessibility', 'design systems'}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things follow from this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: synonyms do not count.&lt;/strong&gt; If the job description says "design systems" and your CV says "component library", you built the same thing and score zero on that term. The system is not reading for meaning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: placement matters.&lt;/strong&gt; A skill mentioned once in a list at the bottom carries less weight than the same skill inside a recent role, where it sits next to a company and a date.&lt;/p&gt;

&lt;p&gt;So the fix is not to invent skills. The fix is to use the employer's vocabulary for work you have genuinely done, and to move it somewhere visible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 3: Filtering — a human still decides
&lt;/h2&gt;

&lt;p&gt;This is where the myths come from. You may have read that a large percentage of CVs are "rejected by robots". I have never found a source for those numbers that holds up, so I do not quote them.&lt;/p&gt;

&lt;p&gt;What is true is more boring. The recruiter opens a ranked list. They look at the top of it. Being on page four is not a rejection, it is an absence.&lt;/p&gt;

&lt;p&gt;Staying visible means three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be parseable&lt;/strong&gt;, so your fields are filled and searchable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be relevant&lt;/strong&gt;, so you rank against this specific job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be readable&lt;/strong&gt;, because a human opens the file straight after.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Optimising for the machine at the cost of the human is a trap. Keyword-stuffed white text still fails when someone actually reads it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How we built CVSet around these three stages
&lt;/h2&gt;

&lt;p&gt;I am the founder and product designer of &lt;strong&gt;CVSet&lt;/strong&gt;. The product exists because of one rule we refused to break: &lt;strong&gt;same facts, better visibility.&lt;/strong&gt; The AI never invents a skill, a job, or a number. It reorganises and highlights what is already in your CV.&lt;/p&gt;

&lt;p&gt;Here is how the flow maps onto what an ATS does.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Analyse — score the CV before anything is changed
&lt;/h3&gt;

&lt;p&gt;You upload a CV and get a score across nine dimensions, including &lt;strong&gt;ATS Keywords&lt;/strong&gt;, &lt;strong&gt;Formatting&lt;/strong&gt;, &lt;strong&gt;Readability&lt;/strong&gt; and &lt;strong&gt;Metrics &amp;amp; Impact&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The same CV is also viewed through four perspectives: &lt;strong&gt;Hiring Manager, Recruiter, Human Eye and Algorithm&lt;/strong&gt;. The Algorithm view is the parsing story above. The Human Eye view is the recruiter who opens the file afterwards. A CV can pass one and fail the other, and you need to see both.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxgq2lzntyeko9bn4xgaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxgq2lzntyeko9bn4xgaj.png" alt="The My CV Scores page, showing several scored CVs with their scores and previous scores." width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scores are shown with an honest ceiling. If a CV cannot reach 100 for a real reason, we do not pretend it can.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Optimise — fix the structural problems
&lt;/h3&gt;

&lt;p&gt;The analysis produces suggestions you resolve one at a time. Formatting suggestions are the ones that protect parsing: single-column flow, contact details in the body, real text instead of images, standard section names.&lt;/p&gt;

&lt;p&gt;One decision here took us a long time to get right. Early on, our formatting logic removed photos from CVs automatically, because photos are unusual on UK and US CVs and can confuse a parser. That was wrong. In Germany and several other markets, a photo is a normal expectation.&lt;/p&gt;

&lt;p&gt;We had built a silent destructive action, which is the worst kind of bug: nothing errors, the user just loses something.&lt;/p&gt;

&lt;p&gt;The fix was to separate two decisions that we had wrongly merged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Format:  [ UK ▾ ]   → sets layout conventions only
Photo:   ( ) Keep photo   ( ) Remove photo   → you must choose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format dropdown never touches the photo. The photo decision is explicit and always per CV. &lt;strong&gt;Never let an automated fix quietly delete the user's content.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqbcswh99tykglttwcazl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqbcswh99tykglttwcazl.png" alt="The Formatting dimension, showing the format dropdown and the separate photo decision row" width="799" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Tailor — match one CV to one job description
&lt;/h3&gt;

&lt;p&gt;This is Stage 2 handled properly. You paste a job description and CVSet evaluates the match across five dimensions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keywords&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prioritisation&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The output tells you which required skills you &lt;strong&gt;already demonstrate&lt;/strong&gt; and which are genuinely missing. Matching skills get the employer's wording and move up the page. Missing ones do not get invented. They go to your &lt;strong&gt;To-Learn List&lt;/strong&gt; instead, which is the honest answer to a gap.&lt;/p&gt;

&lt;p&gt;You can also skip tailoring entirely and use CVSet as a lightweight application tracker.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fage8m42cog5vt6sruz44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fage8m42cog5vt6sruz44.png" alt="The tailoring view with the five dimension scores and the matched vs missing skills" width="800" height="946"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Application Pack — everything after the CV
&lt;/h3&gt;

&lt;p&gt;Once tailored, you can generate a tailored CV, a cover letter, likely interview questions, first-call prep questions and a follow-up email. It is credit-based, so you only generate what you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. AI Report — patterns across applications
&lt;/h3&gt;

&lt;p&gt;After five finalised tailored applications, CVSet aggregates what it has seen: which skills keep appearing in the jobs you target, where your CV consistently loses points, which roles you match best. Five is a real threshold, not a marketing number. Below that, the pattern is noise.&lt;/p&gt;

&lt;p&gt;CVSet runs on a one-time credit purchase with no subscription and no auto-renewal, and data is deleted 30 days after purchase by design.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned building this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parsing failures look like writing failures.&lt;/strong&gt; People rewrite their bullet points when the real problem is a table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-column CVs are not banned, they are risky.&lt;/strong&gt; If you use one, keep the reading order sane and keep contact details in the main flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never guess on the user's behalf.&lt;/strong&gt; Our photo bug taught us that a helpful automatic action is still destructive if the user did not choose it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honesty is a feature.&lt;/strong&gt; Refusing to invent skills costs you a flashy demo and buys you a product people can actually send to an employer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The machine and the human need the same document.&lt;/strong&gt; Anything that helps one at the expense of the other is a bad trade.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An ATS &lt;strong&gt;parses, ranks and filters&lt;/strong&gt;. It does not reject you with an opinion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parsing is the stage most CVs lose.&lt;/strong&gt; Empty fields cannot be searched.&lt;/li&gt;
&lt;li&gt;Keep contact details &lt;strong&gt;in the body&lt;/strong&gt;, use &lt;strong&gt;standard section headings&lt;/strong&gt;, and avoid tables, text boxes and image-only PDFs.&lt;/li&gt;
&lt;li&gt;Keyword matching is &lt;strong&gt;literal&lt;/strong&gt;. Use the employer's wording for work you have really done.&lt;/li&gt;
&lt;li&gt;Skills carry more weight &lt;strong&gt;inside a recent role&lt;/strong&gt; than in a list at the bottom.&lt;/li&gt;
&lt;li&gt;Being unranked is &lt;strong&gt;absence, not rejection&lt;/strong&gt;. Tailor per job to stay near the top.&lt;/li&gt;
&lt;li&gt;Never let a tool add skills you do not have, or remove content you did not agree to lose.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;What is the strangest parsing failure you have hit? I am collecting real examples, and the sidebar-swallows-the-job-history one still wins in our team.&lt;/p&gt;

&lt;p&gt;If this is useful, my next post will go deeper on one thing: &lt;strong&gt;why "demonstrated" has to be decided at tailoring time and stored, instead of being guessed again every time the page loads.&lt;/strong&gt; It sounds like an implementation detail. It changes what the user is allowed to trust.&lt;/p&gt;

&lt;p&gt;You can try the scoring at &lt;a href="https://cvset.io" rel="noopener noreferrer"&gt;cvset.io&lt;/a&gt;.&lt;/p&gt;




</description>
      <category>career</category>
      <category>ai</category>
      <category>ats</category>
      <category>resume</category>
    </item>
    <item>
      <title>Stop Rewriting Your CV: Tailor One Base CV for Every Job Application</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Tue, 01 Sep 2026 08:07:18 +0000</pubDate>
      <link>https://dev.to/ipazooki/stop-rewriting-your-cv-tailor-one-base-cv-for-every-job-application-1pmj</link>
      <guid>https://dev.to/ipazooki/stop-rewriting-your-cv-tailor-one-base-cv-for-every-job-application-1pmj</guid>
      <description>&lt;p&gt;I used to keep six versions of my CV in one folder. &lt;code&gt;CV_final.docx&lt;/code&gt;, &lt;code&gt;CV_final_v2.docx&lt;/code&gt;, &lt;code&gt;CV_final_REAL.docx&lt;/code&gt;. Every application meant twenty minutes of copy, paste and guilt — and I still had no idea if the new version was better than the old one. So we built the thing I wanted: one strong base CV, scored honestly, then tailored per job without touching the facts.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rewriting your CV per job is the wrong loop.&lt;/strong&gt; Fix the base CV once, then tailor it per job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailoring is reordering, not inventing.&lt;/strong&gt; Same facts, better visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score before you edit.&lt;/strong&gt; You cannot improve what you have not measured.&lt;/li&gt;
&lt;li&gt;CVSet does this in three steps: &lt;strong&gt;Analyse → Optimise → Tailor&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Everything below also works manually. The tool just removes the boring part.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The problem with "rewrite it for each job"
&lt;/h2&gt;

&lt;p&gt;Most CV advice says the same thing: &lt;em&gt;tailor your CV for every role&lt;/em&gt;. True, but useless on its own. It leaves you with two bad options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A — send the same CV everywhere.&lt;/strong&gt; Fast, but your best experience sits in the wrong order for that specific role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B — rewrite it every time.&lt;/strong&gt; Slow, and quality drops after application number four. You get tired, you get sloppy, and by Friday you are inventing a "passion for scalable systems" you do not have.&lt;/p&gt;

&lt;p&gt;Both options fail for the same reason. They treat the CV as one object, when it is really &lt;strong&gt;two layers&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Facts&lt;/strong&gt; — what you did, where, when, with what result. These never change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presentation&lt;/strong&gt; — order, wording, emphasis, keywords. These &lt;em&gt;should&lt;/em&gt; change per job.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you separate those layers, the work gets much smaller.&lt;/p&gt;




&lt;h2&gt;
  
  
  The rule we would not break: same facts, better visibility
&lt;/h2&gt;

&lt;p&gt;Before writing a line of code, we set one hard constraint for CVSet:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The AI never adds a skill, a job, a number or an achievement that is not already in your CV.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No invented Kubernetes. No rounded-up team sizes. No "led" when you contributed.&lt;/p&gt;

&lt;p&gt;This sounds limiting. It is actually the whole product. If a tool can invent, you can never trust its output — you have to re-read every line to check it did not lie about you. If it cannot invent, you can trust it and move on.&lt;/p&gt;

&lt;p&gt;So tailoring means only four moves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reorder&lt;/strong&gt; — put the relevant experience higher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reword&lt;/strong&gt; — match the language the job description uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resurface&lt;/strong&gt; — pull a buried bullet point up into view.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trim&lt;/strong&gt; — cut what is not relevant to this role.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is it. Nothing else.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Score the base CV first
&lt;/h2&gt;

&lt;p&gt;Do not tailor a weak CV. You will just produce weak CVs faster.&lt;/p&gt;

&lt;p&gt;CVSet scores your base CV across nine dimensions, including &lt;strong&gt;Metrics &amp;amp; Impact&lt;/strong&gt;, &lt;strong&gt;Readability&lt;/strong&gt;, &lt;strong&gt;ATS Keywords&lt;/strong&gt;, &lt;strong&gt;Clarity &amp;amp; Brevity&lt;/strong&gt; and &lt;strong&gt;Formatting&lt;/strong&gt;. Each one is scored from four perspectives: hiring manager, recruiter, human eye, and algorithm.&lt;/p&gt;

&lt;p&gt;The scores are not decoration. They tell you where the cheapest wins are. A CV scoring low on &lt;strong&gt;Metrics &amp;amp; Impact&lt;/strong&gt; usually has bullets like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Before
- Responsible for improving the checkout flow.

✅ After
- Rebuilt the checkout flow, cutting drop-off from 34% to 21% in one quarter.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same fact. The number was always true — it was just missing from the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2w86vo0yiuz0c9dfzyy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2w86vo0yiuz0c9dfzyy.png" alt="My Applications, empty state. Nothing tracked yet, one clear call to action: " width="800" height="680"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Optimise once, not per job
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Optimise&lt;/strong&gt; step turns the analysis into specific suggestions on specific lines. You resolve them once, on your base CV.&lt;/p&gt;

&lt;p&gt;This is the part people skip, and it is the part that pays. Fixing "no quantification in the last three roles" once improves &lt;strong&gt;every&lt;/strong&gt; future application. Fixing it per job means fixing it forty times.&lt;/p&gt;

&lt;p&gt;Rule of thumb: if a fix would apply to any employer, it belongs in the base CV. If it only makes sense for one company, it belongs in tailoring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Tailor against a real job description
&lt;/h2&gt;

&lt;p&gt;Now the per-job work. You pick which base CV to use, and paste the job description.&lt;/p&gt;

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

&lt;p&gt;Two details in that screen took the longest to get right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple base CVs, not one.&lt;/strong&gt; People applying across countries need different conventions — a German CV and a UK CV are genuinely different documents. Each one keeps its own score.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Continue without tailoring".&lt;/strong&gt; Sometimes you already applied elsewhere and just want to track the application. Forcing tailoring would make the tool annoying, so we made it optional.&lt;/p&gt;

&lt;p&gt;Then it runs. Nine steps, one to two minutes.&lt;/p&gt;

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

&lt;p&gt;We show the step count on purpose. A silent spinner for ninety seconds feels broken. A labelled step feels like work happening.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Read the match score honestly
&lt;/h2&gt;

&lt;p&gt;The tailoring result is a weighted score across five dimensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;match = (skills        × 0.30)
      + (keywords      × 0.25)
      + (language      × 0.20)
      + (quality       × 0.15)
      + (prioritisation× 0.10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Skills&lt;/strong&gt; carries the most weight because it is the least fixable by rewording. If the role needs three years of Kubernetes and you have none, no amount of clever phrasing will close that gap — and the tool should say so, not hide it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fho26pbh032d23cxlvvzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fho26pbh032d23cxlvvzi.png" alt="Applications list with one row: " width="800" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That 51 is the screenshot I nearly did not use. It would have been easy to show a 94.&lt;/p&gt;

&lt;p&gt;But a tool that always says you are great is a tool that tells you nothing. &lt;strong&gt;51 / NEEDS WORK is the honest, useful answer&lt;/strong&gt; — this CV is not ready for this role yet, and here is what is missing. A gap you can see is a gap you can close, either by editing or by applying elsewhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Split your CV into &lt;strong&gt;facts&lt;/strong&gt; (fixed) and &lt;strong&gt;presentation&lt;/strong&gt; (per job). Only the second layer should change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix the base CV once.&lt;/strong&gt; Base-CV improvements compound across every application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailoring = reorder, reword, resurface, trim.&lt;/strong&gt; Never invent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match the employer's vocabulary&lt;/strong&gt;, not your preferred synonym.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;low score you can act on&lt;/strong&gt; beats a high score that flatters you.&lt;/li&gt;
&lt;li&gt;Keep separate base CVs per region if you apply internationally.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Your turn
&lt;/h2&gt;

&lt;p&gt;What is your current process — one master CV, or a folder full of &lt;code&gt;_final_v3&lt;/code&gt; files? And if you tailor manually, what is the one step you refuse to skip? I am curious whether the 10-keyword rule matches what other people do.&lt;/p&gt;

&lt;p&gt;Next post, I want to go deeper into the scoring side: &lt;strong&gt;why we cap some dimensions below 100&lt;/strong&gt;, and why an honest ceiling is more useful than a perfect score.&lt;/p&gt;

&lt;p&gt;You can try the flow at &lt;a href="https://cvset.io" rel="noopener noreferrer"&gt;cvset.io&lt;/a&gt;.&lt;/p&gt;




</description>
      <category>career</category>
      <category>productivity</category>
      <category>ai</category>
      <category>cv</category>
    </item>
    <item>
      <title>Beyond Localhost: Implementing Production-Grade Entra ID Auth in .NET Aspire</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Sun, 10 May 2026 17:03:44 +0000</pubDate>
      <link>https://dev.to/ipazooki/beyond-localhost-implementing-production-grade-entra-id-auth-in-net-aspire-1if0</link>
      <guid>https://dev.to/ipazooki/beyond-localhost-implementing-production-grade-entra-id-auth-in-net-aspire-1if0</guid>
      <description>&lt;p&gt;We’ve all been there. You spend two days wiring up authentication for a distributed project. It works perfectly on your machine with a mock provider or a local database, but the moment you try to integrate a real enterprise identity provider like &lt;strong&gt;Microsoft Entra ID&lt;/strong&gt;, the wheels come off. Redirect URIs don't match, audience validation fails, and your local environment feels nothing like the production environment you're supposed to be targeting.&lt;/p&gt;

&lt;p&gt;When I started building out my latest &lt;a href="https://github.com/iPazooki/CleanArchitecture" rel="noopener noreferrer"&gt;CleanArchitecture template&lt;/a&gt;, I wanted to solve this once and for all. I wanted an architecture where switching between an open-source provider like &lt;strong&gt;Keycloak&lt;/strong&gt; and an enterprise-grade one like &lt;strong&gt;Entra ID&lt;/strong&gt; was a matter of a single configuration switch, not a week of refactoring.&lt;/p&gt;

&lt;p&gt;In this guide, I’m going to walk you through how to implement a production-ready Entra ID authentication flow within a .NET Aspire project. We’re going to look at the architectural "why," the specific Entra portal configuration, and the plumbing required to keep your local development environment sane.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Identity Architecture: The "Three-Registration" Strategy
&lt;/h2&gt;

&lt;p&gt;One of the most common mistakes I see in senior-level designs is trying to use a single Entra App Registration for every component in a distributed system. While it's easier to set up, it’s a security and maintenance nightmare. &lt;/p&gt;

&lt;p&gt;In a true Clean Architecture approach, we separate concerns. In this project, I’ve structured the identity flow around three distinct registrations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The API (The Resource Server):&lt;/strong&gt; This is the gatekeeper. It doesn't handle "logins" or UI. Its sole job is to receive a Bearer token, validate the signature against Microsoft’s keys, and check if the &lt;code&gt;aud&lt;/code&gt; (audience) and &lt;code&gt;scp&lt;/code&gt; (scopes) claims allow the requested action.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The React App (The Public Client):&lt;/strong&gt; This is the Next.js frontend using &lt;code&gt;NextAuth.js&lt;/code&gt;. It performs the "heavy lifting" of the OIDC (OpenID Connect) flow. It interacts with the user, handles the redirect back from Microsoft, and securely stores the tokens.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Scalar UI (The Developer Client):&lt;/strong&gt; Because we use Scalar for API documentation and testing, it needs its own identity. This allows developers to authenticate directly against the API without needing the React frontend running, which is a massive productivity boost.&lt;/li&gt;
&lt;/ol&gt;

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




&lt;h2&gt;
  
  
  Step 1: Entra Portal Configuration
&lt;/h2&gt;

&lt;p&gt;To follow along with the &lt;a href="https://github.com/iPazooki/CleanArchitecture" rel="noopener noreferrer"&gt;CleanArchitecture&lt;/a&gt; repo, you’ll need to create three registrations in the &lt;a href="https://entra.microsoft.com/" rel="noopener noreferrer"&gt;Entra portal&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The API Registration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;CleanArch-API&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supported account types:&lt;/strong&gt; &lt;code&gt;Single Tenant Only&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expose an API:&lt;/strong&gt; Define a new scope called &lt;code&gt;access_as_user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application ID URI:&lt;/strong&gt; Set this to something unique (e.g., &lt;code&gt;api://your-app-id&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The React App Registration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;CleanArch-Web&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Supported account types:&lt;/strong&gt; &lt;code&gt;Single Tenant Only&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Authentication:&lt;/strong&gt; Add a "Web" platform. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Redirect URI:&lt;/strong&gt; &lt;code&gt;http://localhost:65499/api/auth/callback/azure-ad&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Secret:&lt;/strong&gt; Generate a Client Secret (store this safely).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;API Permissions:&lt;/strong&gt; Select "Your APIs" and check the &lt;code&gt;access_as_user&lt;/code&gt; permission from the API registration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. The Scalar Registration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;CleanArch-Scalar&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Supported account types:&lt;/strong&gt; &lt;code&gt;Single Tenant Only&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Authentication:&lt;/strong&gt; Add a "Single-page application" (SPA) platform.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Settings:&lt;/strong&gt; Access tokens (used for implicit flows)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Redirect URI:&lt;/strong&gt; &lt;code&gt;http://localhost:5049/scalar/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 2: Configuring the Frontend (Next.js &amp;amp; NextAuth)
&lt;/h2&gt;

&lt;p&gt;In a .NET Aspire project, the frontend often runs as a Node.js resource. In my project, I use Next.js. The key here is the &lt;code&gt;.env.local&lt;/code&gt; file. I've designed this to be "provider agnostic"—you can toggle between Keycloak and Entra ID.&lt;/p&gt;

&lt;p&gt;For Entra ID, your config should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.local&lt;/span&gt;
&lt;span class="nv"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:5049/
&lt;span class="nv"&gt;NEXTAUTH_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:65499

&lt;span class="c"&gt;# Generate this using: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"&lt;/span&gt;
&lt;span class="nv"&gt;NEXTAUTH_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YourSuperSecretRandomString 

&lt;span class="c"&gt;# The Toggle&lt;/span&gt;
&lt;span class="nv"&gt;AUTH_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Entra 

&lt;span class="c"&gt;# Entra ID Details&lt;/span&gt;
&lt;span class="nv"&gt;ENTRA_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;React-App-Client-ID&amp;gt;
&lt;span class="nv"&gt;ENTRA_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;React-App-Client-Secret&amp;gt;
&lt;span class="nv"&gt;ENTRA_TENANT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;Your-Tenant-ID&amp;gt;
&lt;span class="nv"&gt;ENTRA_SCOPES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openid profile email api://&amp;lt;API-Client-ID&amp;gt;/access_as_user
&lt;span class="nv"&gt;ENTRA_OPENID_CONNECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://login.microsoftonline.com/&amp;lt;Tenant-ID&amp;gt;/v2.0/.well-known/openid-configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Senior Insight:&lt;/strong&gt; Note the &lt;code&gt;ENTRA_SCOPES&lt;/code&gt;. We aren't just asking for &lt;code&gt;openid&lt;/code&gt;. We are explicitly requesting the scope defined in our API registration. Without this, Entra will return an ID Token but a generic Access Token that your API will reject with a 401.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Wiring Up the .NET API
&lt;/h2&gt;

&lt;p&gt;The API sits within the &lt;code&gt;CleanArchitecture.Presentation/API&lt;/code&gt; project. Since we are using .NET Aspire, we want our configuration to be clean and easily overridden by the AppHost.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;appsettings.Development.json&lt;/code&gt;, we configure the &lt;code&gt;AzureAd&lt;/code&gt; section and the &lt;code&gt;ScalarApi&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Entra"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AzureAd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Instance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://login.microsoftonline.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-org.onmicrosoft.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"TenantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ClientId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;API-Client-ID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Audience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;Audience-ID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ScalarApi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;    
    &lt;/span&gt;&lt;span class="nl"&gt;"ClientId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;Scalar-Client-ID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Scopes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openid profile email api://&amp;lt;Your-API-Client-ID&amp;gt;/access_as_user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AuthorizationUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;Authorisation-URL&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Scalar?
&lt;/h3&gt;

&lt;p&gt;Most .NET devs are used to Swagger. However, Scalar provides a much more robust "Try It Out" experience, especially for OAuth2/OIDC. By providing the &lt;code&gt;ScalarApi&lt;/code&gt; config here, the Scalar UI will automatically show an "Authorize" button that initiates the Entra login flow using the Scalar App Registration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: The .NET Aspire Orchestration
&lt;/h2&gt;

&lt;p&gt;This is where the magic happens. In the &lt;code&gt;AppHost&lt;/code&gt; project, we coordinate these services. The beauty of Aspire is that it handles the environment variable injection for us. &lt;/p&gt;

&lt;p&gt;When you run the &lt;code&gt;AppHost&lt;/code&gt;, it reads these settings and ensures that the API and the Web frontend are aware of each other. If you change a port in Aspire, you only have to update your Entra Redirect URIs once, rather than hunting through multiple &lt;code&gt;launchSettings.json&lt;/code&gt; files.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Hard-Learned Lessons &amp;amp; Trade-offs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Managed Identity Pivot
&lt;/h3&gt;

&lt;p&gt;In local development, we use Client Secrets because they are easy. However, the moment this code moves to Azure via &lt;code&gt;azd up&lt;/code&gt;, you should pivot to &lt;strong&gt;Managed Identity&lt;/strong&gt;. .NET Aspire’s Azure components make this easy, but you must ensure your code uses &lt;code&gt;DefaultAzureCredential&lt;/code&gt; and avoid keeping secrets in your configuration files.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No CORS Nightmare (Thanks to the BFF Pattern)
&lt;/h3&gt;

&lt;p&gt;In a traditional SPA → API setup, the browser calls the API directly, which immediately triggers the usual CORS chaos — mismatched origins, blocked &lt;code&gt;Authorization&lt;/code&gt; headers, and endless tweaking of &lt;code&gt;Program.cs&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
But in this project, the &lt;strong&gt;BFF (Backend‑for‑Frontend) pattern&lt;/strong&gt; eliminates that entire class of problems.&lt;/p&gt;

&lt;p&gt;Your React/Next.js app never calls the API from the browser.&lt;br&gt;&lt;br&gt;
Instead, all API traffic flows like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser → Next.js Server (BFF) → .NET API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the browser only communicates with the &lt;strong&gt;same origin&lt;/strong&gt; (your Next.js server), there is &lt;strong&gt;no cross‑origin request&lt;/strong&gt;, and therefore &lt;strong&gt;no CORS configuration is required&lt;/strong&gt; in your .NET API.&lt;/p&gt;

&lt;p&gt;The BFF handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attaching the user’s access token securely on the server
&lt;/li&gt;
&lt;li&gt;Forwarding requests to the API
&lt;/li&gt;
&lt;li&gt;Keeping tokens out of the browser entirely
&lt;/li&gt;
&lt;li&gt;Ensuring the API only ever receives server‑to‑server calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This results in a dramatically simpler and more secure setup.&lt;br&gt;&lt;br&gt;
Local development becomes frictionless, and production behaves exactly the same, no special CORS rules, no exposed tokens, no surprises.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Keycloak as a Fallback
&lt;/h3&gt;

&lt;p&gt;Why did I keep Keycloak in the project? Because Entra ID requires an internet connection and an active Azure subscription. For "offline" development or lightning-fast integration tests, spinning up a Keycloak container via Aspire is a godsend. It ensures that the team can keep working even if Azure is having a bad day.&lt;/p&gt;
&lt;h2&gt;
  
  
  Path to Production: &lt;code&gt;azd up&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;One of the primary reasons to use this specific project structure is the deployment story. Because this is a .NET Aspire project, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Azure Developer CLI will look at your &lt;code&gt;AppHost&lt;/code&gt;, generate the necessary Bicep files, and provision your Azure Container Apps, Key Vault, and Log Analytics. Because we've separated our App Registrations, we can easily map them to environment variables in the Azure production environment, ensuring a seamless transition from "it works on my machine" to "it works in the cloud."&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Identity is often the "final boss" of software architecture. By using .NET Aspire and following the Clean Architecture patterns laid out in &lt;a href="https://github.com/iPazooki/CleanArchitecture" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;, you turn a complex, error-prone manual process into a repeatable, configuration-driven workflow.&lt;/p&gt;

&lt;p&gt;**Have you made the jump to .NET Aspire yet? &lt;br&gt;
What’s been your biggest challenge with Entra ID integration? &lt;/p&gt;

&lt;p&gt;Let’s discuss in the comments.**&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>azure</category>
      <category>aspire</category>
    </item>
    <item>
      <title>Claude Opus 4.6 vs. GPT 5.4</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Sun, 29 Mar 2026 17:10:27 +0000</pubDate>
      <link>https://dev.to/ipazooki/claude-opus-46-vs-gpt-54-4bcc</link>
      <guid>https://dev.to/ipazooki/claude-opus-46-vs-gpt-54-4bcc</guid>
      <description>&lt;h2&gt;
  
  
  Claude Opus 4.6 vs. GPT 5.4: My Take as a C#/.NET Dev on AI Coding Companions
&lt;/h2&gt;

&lt;p&gt;Alright team, let's talk AI. As a senior engineer who's spent more years than I care to admit wrangling C# and .NET, I've seen my fair share of "game-changing" tech. Most of it is just hype. But these new-gen LLMs? They're different. We're talking about legitimate productivity boosters, especially when you're staring down a tricky bug or architecting a new microservice.&lt;/p&gt;

&lt;p&gt;Lately, I've been putting the big two — Claude Opus 4.6 and GPT 5.4 — through their paces specifically for coding tasks. The question isn't &lt;em&gt;if&lt;/em&gt; they're useful, but &lt;em&gt;which one&lt;/em&gt; to bring to the fight, or if we should be thinking "both." Let's dive into my real-world experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup: My C#/.NET AI Playground
&lt;/h2&gt;

&lt;p&gt;Before we get into the nitty-gritty, a quick word on my testing environment. I wasn't just asking them to write "Hello World." I was throwing real-world problems at them: building complex LINQ queries, designing a robust API controller, refactoring legacy code, even trying to get them to write xUnit tests for some tricky asynchronous logic.&lt;/p&gt;

&lt;p&gt;I wanted to see how they handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Context:&lt;/strong&gt; Can they keep track of a larger codebase or conversation?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Precision:&lt;/strong&gt; Do they generate code that actually compiles and runs correctly the first time?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Nuance:&lt;/strong&gt; Can they understand &lt;em&gt;why&lt;/em&gt; I'm asking for something, not just &lt;em&gt;what&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Debugging:&lt;/strong&gt; How good are they at finding issues in their own code or mine?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GPT 5.4: The Speedy Generalist with a Few Surprises
&lt;/h2&gt;

&lt;p&gt;GPT 5.4 feels like that incredibly bright junior developer who's read &lt;em&gt;every&lt;/em&gt; programming book but sometimes misses the specific context of &lt;em&gt;our&lt;/em&gt; project. It's fast, incredibly broad in its knowledge, and often provides surprisingly elegant solutions right out of the gate.&lt;/p&gt;

&lt;p&gt;When I needed boilerplate code for a new &lt;code&gt;DbContext&lt;/code&gt; or a standard ASP.NET Core controller, GPT 5.4 was lightning-fast and usually spot-on. It's fantastic for generating common design patterns or even suggesting different approaches to a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it Shines:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Broad Knowledge Base:&lt;/strong&gt; If it's a common C# pattern, a widely used .NET library, or a general algorithm, GPT 5.4 knows it.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Code Generation Speed:&lt;/strong&gt; It often generates long code blocks quickly, perfect for getting a first draft down.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Exploration:&lt;/strong&gt; Great for brainstorming different ways to solve a problem or exploring new libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where I Pump the Brakes:&lt;/strong&gt;&lt;br&gt;
Sometimes, GPT 5.4 can be a bit &lt;em&gt;too&lt;/em&gt; confident. It occasionally generates plausible-looking code that has subtle bugs, or it might make assumptions about my project that aren't true. I've also found it can "forget" earlier parts of our conversation if the thread gets too long. It's like it gets distracted by the next shiny coding problem.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Claude Opus 4.6: The Meticulous Architect, Slow and Steady
&lt;/h2&gt;

&lt;p&gt;Claude Opus 4.6, on the other hand, feels more like a seasoned architect. It's often slower to respond, but its answers tend to be incredibly thoughtful, detailed, and deeply contextual. It seems to "think" more before responding, often asking clarifying questions or laying out its reasoning step-by-step.&lt;/p&gt;

&lt;p&gt;For complex refactoring tasks, or when I was trying to optimize a specific piece of asynchronous code for performance, Claude truly stood out. It provided not just the code, but the &lt;em&gt;rationale&lt;/em&gt; behind the choices, often citing best practices or potential pitfalls. It felt like pair programming with someone who meticulously considers every angle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it Shines:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Contextual Understanding:&lt;/strong&gt; It maintains context over very long conversations, making it excellent for multi-step tasks or complex debugging.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Deep Reasoning:&lt;/strong&gt; Its explanations are often superb, breaking down complex problems and justifying its code choices.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Fewer Hallucinations:&lt;/strong&gt; I've found it to be more reliable in generating correct, runnable code without subtle errors. It double-checks its work, which is invaluable.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Refactoring &amp;amp; Debugging:&lt;/strong&gt; Excellent at identifying issues in existing code and suggesting robust improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where I Feel the Pinch:&lt;/strong&gt;&lt;br&gt;
The speed. Sometimes, when you just need a quick &lt;code&gt;IEnumerable&lt;/code&gt; extension method or a simple DI setup, waiting for Claude's detailed explanation can feel a bit overkill. It's not a rapid-fire code generator in the same way GPT 5.4 can be.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict: Don't Choose, Combine!
&lt;/h2&gt;

&lt;p&gt;After weeks of real-world use, my conclusion is clear: &lt;strong&gt;you don't have to pick just one.&lt;/strong&gt; These aren't competitors; they're complementary tools in a modern software engineer's arsenal.&lt;/p&gt;

&lt;p&gt;Think of it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reach for GPT 5.4 when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You need rapid prototyping or boilerplate generation.&lt;/li&gt;
&lt;li&gt;  You're exploring new libraries or frameworks and need quick examples.&lt;/li&gt;
&lt;li&gt;  You're stuck on a common problem and need a few different potential solutions fast.&lt;/li&gt;
&lt;li&gt;  You need simple, isolated code snippets.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Reach for Claude Opus 4.6 when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You're working on complex architectural decisions or refactoring significant parts of your codebase.&lt;/li&gt;
&lt;li&gt;  You need detailed explanations, best practices, and a deeper understanding of &lt;em&gt;why&lt;/em&gt; certain code is structured a certain way.&lt;/li&gt;
&lt;li&gt;  You're debugging persistent, tricky issues and need a methodical, logical approach.&lt;/li&gt;
&lt;li&gt;  You have a long, ongoing conversation about a specific problem and need the AI to maintain deep context.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;I often start with GPT 5.4 for initial drafts or quick ideas. Then, if I hit a wall, or if the problem requires more nuanced reasoning, I'll port the conversation (or at least the core problem) over to Claude Opus 4.6 for a more in-depth architectural review or a meticulous debugging session. It’s like having a brilliant junior dev for the grunt work and an experienced architect for the heavy lifting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your AI Pair Programming Partner(s)
&lt;/h2&gt;

&lt;p&gt;Adopting these tools isn't about replacing engineers; it's about augmenting our capabilities. It's like having a super-powered pair programmer who never gets tired and has read the internet. For us C# and .NET folks, understanding the strengths of both Claude Opus 4.6 and GPT 5.4 means we can write better code, faster, and with fewer headaches.&lt;/p&gt;

&lt;p&gt;What are your experiences? Have you found one to be clearly superior for your specific tech stack, or are you also seeing the value in a multi-model approach? Let me know in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>llm</category>
    </item>
    <item>
      <title>C# Keeps Getting Better: My Favourite Modern Language Features</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Sat, 28 Mar 2026 11:48:16 +0000</pubDate>
      <link>https://dev.to/ipazooki/c-keeps-getting-better-my-favourite-modern-language-features-2bkh</link>
      <guid>https://dev.to/ipazooki/c-keeps-getting-better-my-favourite-modern-language-features-2bkh</guid>
      <description>&lt;p&gt;If you've been coding in C# for a while, you know it's a language that refuses to sit still. Every new version brings a fresh set of tools, and honestly, as someone who's been wrangling &lt;code&gt;.NET&lt;/code&gt; for years, it's pretty exciting to see how much more expressive, concise, and just plain &lt;em&gt;fun&lt;/em&gt; C# has become. Gone are the days of endless boilerplate for simple tasks!&lt;/p&gt;

&lt;p&gt;Let's dive into some of the features that have genuinely changed how I approach C# development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streamlining Your Code: Less Ceremony, More Action!
&lt;/h2&gt;

&lt;p&gt;One of the biggest shifts in recent C# versions has been a strong push towards reducing boilerplate. We're all busy, right? Why type more when you can say the same thing with less?&lt;/p&gt;

&lt;h3&gt;
  
  
  Top-Level Statements: Kickstarting Your Apps
&lt;/h3&gt;

&lt;p&gt;Remember when even a simple "Hello World" console app needed a &lt;code&gt;namespace&lt;/code&gt;, a &lt;code&gt;class&lt;/code&gt;, and a &lt;code&gt;static void Main(string[] args)&lt;/code&gt;? It felt a bit heavy for quick scripts or learning examples. Well, say hello to top-level statements!&lt;/p&gt;

&lt;p&gt;Now, your entire program can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, modern C#!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// You can even use async/await directly&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Waiting is over!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fantastic for small utilities, Azure Functions, or just getting an idea off the ground without the cognitive load of a full class structure. It makes C# feel much more scripting-friendly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Using Directives: De-cluttering Your Files
&lt;/h3&gt;

&lt;p&gt;How many times have you scrolled past a dozen &lt;code&gt;using&lt;/code&gt; statements at the top of every file? &lt;code&gt;System&lt;/code&gt;, &lt;code&gt;System.Collections.Generic&lt;/code&gt;, &lt;code&gt;System.Linq&lt;/code&gt;, &lt;code&gt;Microsoft.EntityFrameworkCore&lt;/code&gt;... the list goes on.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;global using&lt;/code&gt; directives let you declare these common &lt;code&gt;using&lt;/code&gt; statements &lt;em&gt;once&lt;/em&gt; for your entire project. Just drop them into a file (often &lt;code&gt;GlobalUsings.cs&lt;/code&gt; or &lt;code&gt;Usings.cs&lt;/code&gt;), and they're available everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GlobalUsings.cs&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...and so on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference this makes in larger projects is immense. Your individual files become cleaner, focusing purely on the code within. No more endless scrolling past &lt;code&gt;using&lt;/code&gt; blocks!&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Embracing Immutability: Records &amp;amp; &lt;code&gt;init&lt;/code&gt; Setters
&lt;/h2&gt;

&lt;p&gt;Immutability is a huge win for robust, predictable software. When an object's state can't change after it's created, you eliminate entire classes of bugs related to unexpected side effects. C# has made it a first-class citizen with records and &lt;code&gt;init&lt;/code&gt; setters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Record Types: The Data-First Class
&lt;/h3&gt;

&lt;p&gt;Records are essentially classes (or structs) designed for immutable data models. They come with a ton of goodies out of the box, like value-based equality, concise syntax, and non-destructive mutation (&lt;code&gt;with&lt;/code&gt; expressions).&lt;/p&gt;

&lt;p&gt;Imagine you're defining a Data Transfer Object (DTO) or an entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Old way (class) - lots of boilerplate for immutability, equality, and ToString&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Need to override Equals, GetHashCode, ToString manually...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// New way (record) - concise, immutable by default, value equality&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's right, a single line! Records are perfect for scenarios where you just need to represent data. They save you from writing mountains of boilerplate for &lt;code&gt;Equals&lt;/code&gt;, &lt;code&gt;GetHashCode&lt;/code&gt;, and &lt;code&gt;ToString&lt;/code&gt;, and they promote a functional style of programming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--V_S5bB_0--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3f3f3f3f3f3f3f3f3f3f3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--V_S5bB_0--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3f3f3f3f3f3f3f3f3f3f3.png" alt="Technical Diagram Placeholder" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;init&lt;/code&gt; Setters: Immutable Properties, Flexible Creation
&lt;/h3&gt;

&lt;p&gt;Even with regular classes, &lt;code&gt;init&lt;/code&gt; setters provide a powerful way to ensure properties can only be set during object initialisation, enforcing immutability after creation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Can only be set during object creation&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;RegisteredDate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Default value also works&lt;/span&gt;

    &lt;span class="c1"&gt;// Usage&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;UserProfile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"devfriend"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;// user.Id = 2; // Error: Init-only setter cannot be used here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the best of both worlds: object initialiser syntax for easy construction, and immutability once the object is fully formed. Super handy for configuration objects or read-only view models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smarter Decisions: Enhanced Pattern Matching
&lt;/h2&gt;

&lt;p&gt;Pattern matching isn't new, but it's been steadily enhanced, making complex conditional logic incredibly readable and concise. It allows you to test an expression against various "patterns" and execute code based on a match.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;switch&lt;/code&gt; Expressions and Property Patterns
&lt;/h3&gt;

&lt;p&gt;Gone are the days of clunky &lt;code&gt;switch&lt;/code&gt; statements with &lt;code&gt;case&lt;/code&gt; and &lt;code&gt;break&lt;/code&gt;. &lt;code&gt;switch&lt;/code&gt; expressions are, well, expressions – they return a value, making them perfect for transformations. Combine that with property patterns, and you have some serious power.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Imagine a DTO or some input object&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ItemType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderItem&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// If ItemType is "Book" AND Quantity &amp;gt; 5, apply 10% discount&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ItemType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Quantity&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnitPrice&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.10M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// If ItemType is "Software" AND UnitPrice &amp;gt; 100, apply 5% discount&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ItemType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Software"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UnitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Quantity&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnitPrice&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.05M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Any other case&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0M&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Even simpler: type patterns&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetShapeInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Circle&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Radius&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"Large Circle with radius &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Radius&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Circle&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"Small Circle with radius &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Radius&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Rectangle&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"Rectangle &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Unknown shape"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This syntax is incredibly powerful for expressing intent clearly. Instead of nested &lt;code&gt;if/else if&lt;/code&gt; statements, you get a clean, declarative structure that's easy to read and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;These are just a few of my personal highlights from the recent evolutions of C#. From making quick scripts easier with top-level statements to building robust, immutable data models with records, and writing more expressive conditional logic with pattern matching, C# continues to empower developers to write cleaner, more maintainable code.&lt;/p&gt;

&lt;p&gt;It's a fantastic time to be a C# developer, and I'm always excited to see what's next!&lt;/p&gt;

&lt;p&gt;What new C# feature has made the biggest difference in your daily coding life? Let me know in the comments!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>net</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Migration from .NET 4.7 to .NET 10</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Sun, 07 Dec 2025 14:32:14 +0000</pubDate>
      <link>https://dev.to/ipazooki/migration-from-net-47-to-net-10-4ig0</link>
      <guid>https://dev.to/ipazooki/migration-from-net-47-to-net-10-4ig0</guid>
      <description>&lt;h2&gt;
  
  
  Travels in Time:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Great Migration from .NET Framework 4.7 to .NET 10 🚀
&lt;/h3&gt;

&lt;p&gt;All of us have encountered this situation: inheriting a project that has remained untouched for years, only to discover it is now outdated. Recently, I was assigned to such a project with a clear directive to modernise a legacy application built on .NET Framework 4.7 and successfully migrate it to the latest LTS release, &lt;strong&gt;.NET 10&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It was a journey filled with architectural archaeology, AI tooling, and some tough decisions. Here is the story of how we managed the upgrade of this legacy application using Visual Studio 2026, and what we learned along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧐 Phase 1: The Archaeology
&lt;/h2&gt;

&lt;p&gt;My journey began with a classic induction meeting with my Line Manager and Product Owner. Once the pleasantries were done, I popped the hood to see how the project was structured.&lt;/p&gt;

&lt;p&gt;As expected with legacy code, there wasn't much of a "structure" to speak of. It was a flat architecture where almost all business logic lived directly inside the Controllers. Even worse, database contexts were being instantiated right there in the controller methods, something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LegacyController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Direct instantiation inside the controller? Check.&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MyDbContext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I knew that if I started refactoring immediately, I would generate a massive amount of changes that would be impossible to review properly, increasing the risk of failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Initial Health Check
&lt;/h3&gt;

&lt;p&gt;I decided to run the application to understand the flow without getting bogged down in the nitty-gritty of every single class. I also ran the existing unit tests. Unsurprisingly, red lights flashed everywhere.&lt;/p&gt;

&lt;p&gt;The project lacked a proper CI/CD pipeline, so over time, business logic had drifted, while the tests had been forgotten.&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Phase 2: The Battle Plan
&lt;/h2&gt;

&lt;p&gt;I went back to the Azure Board and tagged my Line Manager and PO with a plan. I proposed a strict order of operations to mitigate risk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Stabilize:&lt;/strong&gt; Pass all current unit tests to ensure the application works &lt;em&gt;before&lt;/em&gt; we touch the framework.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Upgrade:&lt;/strong&gt; Move the project directly from .NET Framework 4.7 to .NET 10.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Modernise:&lt;/strong&gt; Once stable on .NET 10, implement a proper CI/CD pipeline in Azure DevOps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I spent a few days fixing bugs in the test suite until everything was green. Only then was I ready for the big lift.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠 Phase 3: The Upgrade (Dependencies &amp;amp; AI Tools)
&lt;/h2&gt;

&lt;p&gt;Using the newly released &lt;strong&gt;Visual Studio 2026&lt;/strong&gt;, I started by auditing our NuGet packages and third-party references.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Third-Party Challenge
&lt;/h3&gt;

&lt;p&gt;One significant hurdle was a reference to &lt;strong&gt;Excel-DNA&lt;/strong&gt;. When upgrading, checking vendor compatibility is non-negotiable. I had to comb through their documentation to find their specific upgrade process for .NET 10 compatibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Entity Framework Dilemma
&lt;/h3&gt;

&lt;p&gt;We were also using &lt;strong&gt;Entity Framework 6 (EF6)&lt;/strong&gt;. While EF6 is technically compatible with .NET 10, the long-term goal is obviously Entity Framework Core. However, trying to do that migration simultaneously would have blown up the scope. My manager and I agreed: get to .NET 10 first, upgrade to EF Core later.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI vs. Legacy Tools
&lt;/h3&gt;

&lt;p&gt;Visual Studio 2026 offers a tool called &lt;strong&gt;Modernizer&lt;/strong&gt;, which leverages AI (ChatGPT, Gemini, Claude) to handle upgrades.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Modernizer (AI)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fantastic for simple class libraries. It proposed excellent upgrade plans and did the heavy lifting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Legacy Upgrade Assistant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Necessary for the complex UI projects. The AI struggled with our old AngularJS/MVC mix, so I had to enable this legacy tool via &lt;code&gt;Tools &amp;gt; Options&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🏗 Phase 4: Execution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Low-Hanging Fruit
&lt;/h3&gt;

&lt;p&gt;I started with the solution's four projects. Three were simple class libraries, so I used the &lt;strong&gt;Modernizer&lt;/strong&gt; AI tool to upgrade them to .NET 10 first.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; When upgrading, resist the urge to use new features immediately. .NET 10 has cool toys like file-scoped namespaces, but mixing feature changes with framework upgrades creates noisy Pull Requests (PRs). I kept the code changes minimal so my team could actually review the PR.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2: The Web Project (The Beast)
&lt;/h3&gt;

&lt;p&gt;The main website was an ASP.NET MVC application with &lt;strong&gt;AngularJS&lt;/strong&gt;. The Legacy Upgrade Assistant handled the C# conversion well, but it had no idea what to do with the UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Bundling Problem:&lt;/strong&gt;&lt;br&gt;
Old ASP.NET had built-in bundling for CSS and JS. .NET 10 doesn't work that way. I had to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Find npm equivalents for our NuGet client-side packages.&lt;/li&gt;
&lt;li&gt; Implement a new build process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I decided to use &lt;strong&gt;Gulp&lt;/strong&gt; to compile our TypeScript and LESS files and bundle them. I utilised AI heavily to generate the Gulp tasks (which worked perfectly), creating a single output file referenced in our &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: The Inevitable Refactor
&lt;/h3&gt;

&lt;p&gt;Here is where I had to break my own rule about "minimal changes." The old code was so non-SOLID that I literally couldn't inject the services I needed into the controllers.&lt;/p&gt;

&lt;p&gt;I had to refactor the presentation layer, introducing the &lt;strong&gt;Unit of Work&lt;/strong&gt; pattern and cleaning up the dependency injection. It made the PR larger, but it was unavoidable to get the application running.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Phase 5: Crossing the Finish Line
&lt;/h2&gt;

&lt;p&gt;With the refactoring done, I ran the project. Miraculously (or perhaps due to the preparation), it worked.&lt;/p&gt;

&lt;p&gt;I performed end-to-end testing, deployed to the test environment, and handed it over to QA. Everything looked green. I opened the PR, which was only "50% of the job" considering the future work needed, but the platform was now on .NET 10.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔮 What's Next?
&lt;/h2&gt;

&lt;p&gt;We successfully reached .NET 10, but the journey isn't over. We still need to upgrade to EF Core and add observability.&lt;/p&gt;

&lt;p&gt;However, the business has shifted priorities. The old AngularJS frontend is now a security vulnerability. We aren't just upgrading to modern Angular; my manager wants a complete rewrite of the presentation layer using &lt;strong&gt;React&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There’s just one catch: I don't know React very well yet.&lt;/p&gt;

&lt;p&gt;It’s going to be a challenging task, but I plan to tackle it head-on. Stay tuned, I’ll be sharing how I manage that learning curve in my next post.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
    </item>
    <item>
      <title>🚀 Event-Driven Architecture (EDA): The Power of Reacting to Events</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Mon, 03 Nov 2025 07:48:16 +0000</pubDate>
      <link>https://dev.to/ipazooki/event-driven-architecture-eda-the-power-of-reacting-to-events-1eb7</link>
      <guid>https://dev.to/ipazooki/event-driven-architecture-eda-the-power-of-reacting-to-events-1eb7</guid>
      <description>&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/jinuKuAwlx4"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Hey everyone 👋&lt;/p&gt;

&lt;p&gt;Today, we’re diving into one of my favourite software architecture topics, &lt;strong&gt;Event-Driven Architecture (EDA)&lt;/strong&gt;. It’s one of those patterns that sounds a bit fancy at first, but once you understand it, you start seeing it &lt;em&gt;everywhere&lt;/em&gt;: in cloud applications, microservices, and even your daily notifications!&lt;/p&gt;

&lt;p&gt;Let’s break it down step by step. ☕&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 What Is Event-Driven Architecture?
&lt;/h2&gt;

&lt;p&gt;At its core, &lt;strong&gt;Event-Driven Architecture&lt;/strong&gt; is all about &lt;em&gt;reacting to events&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;event&lt;/strong&gt; is simply &lt;em&gt;something that happened&lt;/em&gt;, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An order was placed 🛒&lt;/li&gt;
&lt;li&gt;A payment was completed 💳&lt;/li&gt;
&lt;li&gt;A user logged in 👤&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of having tightly coupled services that directly call each other (as in layered or traditional microservice architectures), &lt;strong&gt;EDA&lt;/strong&gt; uses &lt;em&gt;events&lt;/em&gt; as the main form of communication.&lt;/p&gt;

&lt;p&gt;Here’s where the magic happens ✨:&lt;br&gt;
In EDA, each service acts independently. When something happens, a service simply &lt;em&gt;publishes&lt;/em&gt; an event, it’s like saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Hey, an order was placed!”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But here’s the cool bit: the service that emits the event doesn’t know (or care) who is listening, when they’ll react, or how they’ll process it.&lt;/p&gt;

&lt;p&gt;That’s a big contrast to traditional architectures, where services often directly call one another through APIs.&lt;/p&gt;

&lt;p&gt;EDA, on the other hand, is &lt;em&gt;asynchronous&lt;/em&gt; and &lt;em&gt;decoupled&lt;/em&gt;. Services communicate via an &lt;strong&gt;event broker&lt;/strong&gt; (or message queue), meaning they’re completely independent; they just publish events into a queue without caring who processes them later.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Why EDA Matters
&lt;/h2&gt;

&lt;p&gt;This independence makes EDA:&lt;br&gt;
✅ Highly scalable&lt;br&gt;
✅ Fault-tolerant&lt;br&gt;
✅ Easier to evolve over time&lt;/p&gt;

&lt;p&gt;But it’s not always simple. While EDA offers powerful flexibility, it also introduces &lt;strong&gt;complexity&lt;/strong&gt;. So before jumping in, we need to answer a few important questions 👇&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Key Questions to Understand EDA
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What’s the difference between an &lt;strong&gt;event&lt;/strong&gt; and a &lt;strong&gt;message&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;How do we ensure &lt;strong&gt;no data loss&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;How many &lt;strong&gt;brokers or queues&lt;/strong&gt; do we need?&lt;/li&gt;
&lt;li&gt;How do we &lt;strong&gt;track state&lt;/strong&gt; across multiple services?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s explore each one.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 Events vs. Messages
&lt;/h2&gt;

&lt;p&gt;In EDA, the main concept is an &lt;strong&gt;event&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When an event is raised, we don’t care who processes it or when. The system just publishes the event, &lt;em&gt;fire and forget&lt;/em&gt;. 🔥&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Events&lt;/strong&gt;: Fire and forget. The publisher emits and moves on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messages&lt;/strong&gt;: Commands or queries, requests that expect a result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, in &lt;strong&gt;message-driven architecture&lt;/strong&gt;, a message waits for a response, while in &lt;strong&gt;event-driven architecture&lt;/strong&gt;, we publish and continue working asynchronously.&lt;/p&gt;

&lt;p&gt;Most EDA systems use a &lt;strong&gt;Pub/Sub pattern&lt;/strong&gt;, one service &lt;em&gt;publishes&lt;/em&gt; events and others &lt;em&gt;subscribe&lt;/em&gt; to them.&lt;br&gt;
In contrast, message-driven systems usually use &lt;strong&gt;point-to-point communication&lt;/strong&gt; since the sender knows exactly which service should handle the message.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 Ensuring No Data Loss
&lt;/h2&gt;

&lt;p&gt;One of the biggest challenges in EDA is &lt;strong&gt;data integrity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A service publishes an event to a queue, but right after that, the queue crashes, and the event is gone! 😱&lt;/li&gt;
&lt;li&gt;Or maybe the consumer reads the event but fails before saving it to the database, and the data is lost again!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To prevent this, we can use the &lt;strong&gt;Event Forwarding Pattern&lt;/strong&gt; 🛡️&lt;/p&gt;

&lt;p&gt;Here’s how it helps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Persist Events&lt;/strong&gt;, Before a service sends an event to the queue, it first &lt;em&gt;persists&lt;/em&gt; it in durable storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recover Gracefully&lt;/strong&gt;, If a service crashes after reading an event, it can re-fetch it later because the queue retained a persisted copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track Consumers&lt;/strong&gt;, Store client IDs so each service knows which messages it has processed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm Completion&lt;/strong&gt;, After saving to the database, the service confirms success so the event can safely be removed from the queue.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ensures strong data integrity ✅, but it comes with trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower performance (because of synchronous writes)&lt;/li&gt;
&lt;li&gt;Possible duplicate data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, use this approach when &lt;strong&gt;data reliability&lt;/strong&gt; is more important than &lt;strong&gt;throughput&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ How Many Brokers Do We Need?
&lt;/h2&gt;

&lt;p&gt;Good question! Should each microservice have its own broker, or should they all share one?&lt;/p&gt;

&lt;p&gt;Let’s look at the concept of &lt;strong&gt;Architectural Quantum&lt;/strong&gt;, introduced in &lt;em&gt;Building Evolutionary Architectures&lt;/em&gt; 🧠&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An &lt;em&gt;Architectural Quantum&lt;/em&gt; is an independently deployable component with high functional cohesion that includes everything needed for the system to work properly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If all services depend on a single broker, then if that broker goes down, &lt;em&gt;everything&lt;/em&gt; goes down.&lt;br&gt;
This means the entire system is one single &lt;em&gt;architectural quantum&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But if each service has its own broker, then you have multiple independent systems, more resilient, but also more expensive 💸.&lt;/p&gt;

&lt;p&gt;A balanced approach is the &lt;strong&gt;Domain Broker Pattern&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each &lt;em&gt;domain&lt;/em&gt; gets one broker.&lt;/li&gt;
&lt;li&gt;Services within a domain share the same broker.&lt;/li&gt;
&lt;li&gt;Domains remain independent of one another.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you a nice balance between &lt;strong&gt;cost&lt;/strong&gt; and &lt;strong&gt;independence&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Managing State in EDA
&lt;/h2&gt;

&lt;p&gt;Tracking the state of an entity (like an order) across distributed services can be tricky. Let’s say you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;em&gt;Order Service&lt;/em&gt; 🛒&lt;/li&gt;
&lt;li&gt;An &lt;em&gt;Inventory Service&lt;/em&gt; 📦&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;Delivery Service&lt;/em&gt; 🚚&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a user asks, “What’s the status of my order?”, where should we look?&lt;/p&gt;

&lt;p&gt;There are two main patterns for managing state:&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ Push-Based Pattern
&lt;/h3&gt;

&lt;p&gt;Each service writes status updates to a &lt;em&gt;shared queue&lt;/em&gt;.&lt;br&gt;
The &lt;em&gt;Order Service&lt;/em&gt; listens to this queue and keeps the latest status in its database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centralized status tracking&lt;/li&gt;
&lt;li&gt;Easy to query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All services depend on one queue → &lt;em&gt;Architectural quantum = 1&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;A single failure can bring down the whole system&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2️⃣ Pull-Based Pattern
&lt;/h3&gt;

&lt;p&gt;In this model, a separate &lt;em&gt;Status Service&lt;/em&gt; listens to events from all brokers.&lt;br&gt;
It collects and stores the latest status for each order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No shared dependency,  more resilient&lt;/li&gt;
&lt;li&gt;Central point for querying order status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires building and maintaining an extra service&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🏁 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Event-Driven Architecture is incredibly powerful; it enables &lt;strong&gt;scalable&lt;/strong&gt;, &lt;strong&gt;resilient&lt;/strong&gt;, and &lt;strong&gt;decoupled&lt;/strong&gt; systems. But like any architecture, it’s not a one-size-fits-all solution.&lt;/p&gt;

&lt;p&gt;EDA shines when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have many independent services that react to real-time changes&lt;/li&gt;
&lt;li&gt;You need asynchronous communication&lt;/li&gt;
&lt;li&gt;You care more about flexibility and responsiveness than strict performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, it adds complexity in state management, data integrity, and broker design, so plan carefully before adopting it.&lt;/p&gt;




&lt;p&gt;Thanks for reading! 🙌&lt;br&gt;
If you found this useful, don’t forget to share it with your fellow developers and let’s keep learning together 💬&lt;/p&gt;

</description>
      <category>programming</category>
      <category>eventdriven</category>
      <category>tutorial</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Proper Logging in ASP.NET Core</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Wed, 19 Mar 2025 06:49:53 +0000</pubDate>
      <link>https://dev.to/ipazooki/proper-logging-in-aspnet-core-4l61</link>
      <guid>https://dev.to/ipazooki/proper-logging-in-aspnet-core-4l61</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/d6HPSdiP0lw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Hey there, fellow code warriors! 👋 Today, we're diving deep into &lt;strong&gt;proper logging in ASP.NET Core&lt;/strong&gt;. If you've ever found yourself lost in a sea of mysterious bugs or scratching your head over your application's behaviour, this guide is your lighthouse! 🏝️&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Why Logging Matters 🤔
&lt;/h2&gt;

&lt;p&gt;Before we jump into the code, let's talk about why logging is the unsung hero of software development. Proper logging is like giving your future self (and your team) a roadmap to understand what's happening in your application. It's not just about catching errors; it's about gaining insights, improving performance, and making your development life easier.&lt;/p&gt;

&lt;p&gt;Here's why logging is crucial for your projects:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Application Behavior Monitoring&lt;/strong&gt;: It's like having a 24/7 surveillance camera on your code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Capturing&lt;/strong&gt;: Every important moment in your app's life, documented.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Insights&lt;/strong&gt;: Because sometimes, your app needs a health check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But logging isn't just about writing stuff down. It's part of a trio of concepts that make your applications robust, maintainable, and scalable:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Holy Trinity of Application Observability 🛠️
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Logging&lt;/strong&gt;: Your app's diary 📔&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Records events, errors, and important information&lt;/li&gt;
&lt;li&gt;Helps debug issues quickly&lt;/li&gt;
&lt;li&gt;Provides insights into application behaviour&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traceability&lt;/strong&gt;: Following the breadcrumbs of your code 🍞&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracks the flow of application processes&lt;/li&gt;
&lt;li&gt;Follows the lifecycle of requests or commands&lt;/li&gt;
&lt;li&gt;Especially helpful for distributed systems or microservices&lt;/li&gt;
&lt;li&gt;Facilitates debugging and enhances reliability&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;: Measuring what matters 📊&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides measurable data about application performance&lt;/li&gt;
&lt;li&gt;Helps make data-driven decisions&lt;/li&gt;
&lt;li&gt;Enhances user experiences through targeted optimizations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h2&gt;
  
  
  Implementing Logging with Serilog in ASP.NET Core 🛠️
&lt;/h2&gt;

&lt;p&gt;Let's roll up our sleeves and get our hands dirty with some code! We'll be using Serilog, a powerful logging library for .NET.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Serilog
&lt;/h3&gt;

&lt;p&gt;First, add the Serilog.AspNetCore NuGet package to your project. If you're planning to use Azure Application Insights, grab Serilog.Sinks.AzureApp too.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;Program.cs&lt;/code&gt;, set up Serilog like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ClearProviders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSerilog&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFrom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code clears any existing logging providers and tells Serilog to read its configuration from the &lt;code&gt;appsettings.json&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Serilog
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;appsettings.json&lt;/code&gt;, you can configure Serilog like a boss:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Serilog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Using"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Serilog.Sinks.Console"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Serilog.Sinks.File"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"MinimumLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Information"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Override"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Microsoft"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"System"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warning"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"WriteTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Console"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"theme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"outputTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"File"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Logs/log-.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"rollingInterval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Day"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"rollOnFileSizeLimit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"outputTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"fileSizeLimitBytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1073741824&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AzureApp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"instrumentationKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_instrumentation_key"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Enrich"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"FromLogContext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"WithMachineName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"WithThreadId"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration sets up Serilog to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use both console and file sinks&lt;/li&gt;
&lt;li&gt;Set default log levels (Information for production, Debug for development)&lt;/li&gt;
&lt;li&gt;Write logs to the console and a file&lt;/li&gt;
&lt;li&gt;Send logs to Azure Application Insights&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Logging in Action: GlobalExceptionHandler 🎭
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;GlobalExceptionHandler&lt;/code&gt; class is like a superhero for exceptions. It catches them, logs them, and sends back a nicely formatted response to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;TryHandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;httpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... (exception handling logic)&lt;/span&gt;

    &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"An error occurred while processing the request {DateTime} {Path}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;httpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (return problem details)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class inherits from &lt;code&gt;IExceptionHandler&lt;/code&gt; and is registered in our dependency injection setup. It formats errors based on their type and logs them in a structured way before returning a user-friendly response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Logging with LoggingBehaviour 🕵️
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;LoggingBehaviour&lt;/code&gt; class is our secret agent, tracking every request that passes through our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequestHandlerDelegate&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Stopwatch&lt;/span&gt; &lt;span class="n"&gt;stopWatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Handling request {Name}, {DateTime} "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (request handling)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopWatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SlowRequestThresholdMilliseconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request took too long to handle {Name}, {DateTime}, {ElapsedMilliseconds}ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopWatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSuccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request handled successfully {Name}, {DateTime}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request failed to handle {Name}, {DateTime}, {@Error}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (log success or failure)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class implements &lt;code&gt;IPipelineBehavior&lt;/code&gt; from the MediatR library, acting as a pipeline for each request. It measures request duration, logs warnings for slow requests, and records the outcome of each request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro Tips for Logging Like a Boss 😎
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use Structured Logging&lt;/strong&gt;: Always include relevant context in your logs. Serilog makes this easy with templates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log Levels Matter&lt;/strong&gt;: Use the right log level for the right situation. Not everything is an error!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Matters&lt;/strong&gt;: Logging can impact performance, so use it wisely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive Data Alert&lt;/strong&gt;: Be careful not to log sensitive information. Your logs shouldn't be a security risk!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the "@" Symbol&lt;/strong&gt;: When logging objects with Serilog, use the "@" symbol (e.g., &lt;code&gt;{@Error}&lt;/code&gt;) to serialize the entire object. This makes querying logs easier, especially with tools like Seq.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping Up 🎁
&lt;/h2&gt;

&lt;p&gt;Proper logging is an essential skill for any developer working with ASP.NET Core. It's not just about catching errors; it's about gaining insights, improving performance, and making your development life easier.&lt;/p&gt;

&lt;p&gt;Remember, your logs are your best friend when things go wrong (and even when they don't). They're your application's black box, recording every important event and helping you understand what's happening under the hood.&lt;/p&gt;

&lt;p&gt;So, the next time you're debugging a tricky issue or trying to understand why your app is behaving weirdly, remember: that your logs are there to help. Happy logging, developers! 🚀&lt;/p&gt;

&lt;p&gt;The source code is available in my &lt;a href="https://github.com/iPazooki/CleanArchitecture" rel="noopener noreferrer"&gt;Github&lt;/a&gt; repo.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>aspnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>JWT Authentication &amp; Authorization in ASP.NET</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Tue, 04 Mar 2025 18:06:31 +0000</pubDate>
      <link>https://dev.to/ipazooki/jwt-authentication-authorization-in-aspnet-1j9</link>
      <guid>https://dev.to/ipazooki/jwt-authentication-authorization-in-aspnet-1j9</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ZCoc8s1X2JY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Hey everyone, welcome to our deep dive into JWTs in ASP.NET Core! Whether you’re a beginner or a seasoned developer, this article will guide you through the ins and outs of implementing secure authentication and authorization using JSON Web Tokens (JWT). Grab your favourite beverage, sit back, and let’s get started! 😊&lt;/p&gt;




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

&lt;h2&gt;
  
  
  What is JWT? 🔍
&lt;/h2&gt;

&lt;p&gt;JWT stands for &lt;strong&gt;JSON Web Token&lt;/strong&gt;. Think of it as a digital passport that securely transmits information between parties. It’s &lt;strong&gt;compact&lt;/strong&gt;, &lt;strong&gt;URL-safe&lt;/strong&gt;, and &lt;strong&gt;self-contained&lt;/strong&gt;, meaning all the necessary data for authentication is right inside the token. A JWT consists of three key parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Header:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Specifies the token type and the hashing algorithm used (e.g., HMAC SHA256).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Payload:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Contains the &lt;strong&gt;claims&lt;/strong&gt;—statements about an entity (usually the user) such as their ID, email, and roles. These claims are essential for both authentication and authorization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Signature:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Ensures that the token hasn’t been tampered with. It’s created using the header, payload, and a secret key.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can explore JWTs and see their inner workings at &lt;a href="https://jwt.io" rel="noopener noreferrer"&gt;jwt.io&lt;/a&gt; 🔗.&lt;/p&gt;




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

&lt;h2&gt;
  
  
  How Does the Authentication &amp;amp; Authorization Process Work? 🛡️
&lt;/h2&gt;

&lt;p&gt;When a client sends a request to a secure API, here’s what happens behind the scenes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client Request:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The client sends a request including the JWT in the &lt;code&gt;Authorization&lt;/code&gt; header.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ASP.NET authentication provider extracts the JWT.
&lt;/li&gt;
&lt;li&gt;It validates the token’s signature, expiration, and issuer.
&lt;/li&gt;
&lt;li&gt;If valid, the user is authenticated.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authorization:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server checks if the user has the required &lt;strong&gt;claims&lt;/strong&gt; to access the resource.
&lt;/li&gt;
&lt;li&gt;If the claims match the required policy, access is granted; otherwise, a &lt;code&gt;403 Forbidden&lt;/code&gt; error is returned.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpbcizrkm95wiocckhyw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpbcizrkm95wiocckhyw.gif" alt="Policy-Based vs. Role-Based Access Control" width="513" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy-Based vs. Role-Based Access Control 🔐
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Role-Based Access Control (RBAC)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RBAC&lt;/strong&gt; assigns roles directly to users.
&lt;/li&gt;
&lt;li&gt;For example, only users with an &lt;strong&gt;Admin&lt;/strong&gt; role might access certain endpoints.
&lt;/li&gt;
&lt;li&gt;This method can become unwieldy as your user base grows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Policy-Based Access Control
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Policy-Based&lt;/strong&gt; access control defines policies (like &lt;code&gt;CanRead&lt;/code&gt;) that are assigned to roles.
&lt;/li&gt;
&lt;li&gt;When a user’s role changes, their permissions update automatically without modifying each user individually.
&lt;/li&gt;
&lt;li&gt;This approach is more flexible and scalable, especially for larger applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our demo, we focus on policy-based access control for that extra bit of flexibility!&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementing JWT in ASP.NET Core: A Step-by-Step Guide 💻
&lt;/h2&gt;

&lt;p&gt;Let’s walk through how to set up JWT authentication in an ASP.NET Core application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clone the project
&lt;/h3&gt;

&lt;p&gt;Clone the project from &lt;a href="https://github.com/iPazooki/CleanArchitecture" rel="noopener noreferrer"&gt;Github&lt;/a&gt; and take a look at the authentication and authorisation configuration in the &lt;code&gt;DependencyInjection.cs&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JwtBearerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PermissionAuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuthorizationPolicyProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PermissionAuthorizationPolicyProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have all authentication configuration in the &lt;code&gt;JwtBearerOptionsSetup&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JwtBearerOptionsSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JwtOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;jwtOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;IPostConfigureOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JwtBearerOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;JwtOptions&lt;/span&gt; &lt;span class="n"&gt;_jwtOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PostConfigure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JwtBearerOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequireHttpsMetadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ValidateIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidateAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Audience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidateIssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;IssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SymmetricSecurityKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SecretKey&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;ValidateLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ClockSkew&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup ensures that every token is validated against your issuer, audience, and signing key.&lt;/p&gt;

&lt;p&gt;To add permission authorisation, we have added two files &lt;code&gt;PermissionAuthorizationHandler&lt;/code&gt; and &lt;code&gt;PermissionAuthorizationPolicyProvider&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;PermissionAuthorizationHandler&lt;/code&gt; we make sure that the request has enough permission:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PermissionAuthorizationHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PermissionRequirement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;HandleRequirementAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationHandlerContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PermissionRequirement&lt;/span&gt; &lt;span class="n"&gt;requirement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToHashSet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requirement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requirement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, only requests with a valid JWT will be able to access these endpoints.&lt;/p&gt;

&lt;p&gt;Another important file is &lt;code&gt;JwtProvider&lt;/code&gt; which is responsible for creating the JWT token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JwtProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JwtOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;jwtOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IPermissionService&lt;/span&gt; &lt;span class="n"&gt;permissionService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IJwtProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;JwtOptions&lt;/span&gt; &lt;span class="n"&gt;_jwtOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateJwtTokenAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JwtRegisteredClaimNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JwtRegisteredClaimNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;!),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JwtRegisteredClaimNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Jti&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;permissionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPermissionsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="n"&gt;SigningCredentials&lt;/span&gt; &lt;span class="n"&gt;signingCredentials&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SymmetricSecurityKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SecretKey&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;SecurityAlgorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HmacSha256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;JwtSecurityToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Audience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_jwtOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeoutMinutes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;signingCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;signingCredentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JwtSecurityTokenHandler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;WriteToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all the configuration we need to know about JWT authentication and authorisation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap-Up &amp;amp; Key Takeaways 📝
&lt;/h2&gt;

&lt;p&gt;Today, we explored:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JWT Fundamentals:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Understanding JWT as a secure, compact way to transfer claims between parties.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication &amp;amp; Authorization Flow:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
From initial user login to token validation and access control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Policy-Based Access Control:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Offering flexibility over traditional role-based approaches by defining policies like &lt;code&gt;CanRead&lt;/code&gt; that are associated with roles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementation in ASP.NET Core:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A step-by-step guide including configuration, creating a login endpoint, and securing your APIs with JWT.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementing JWT in your ASP.NET Core applications not only simplifies authentication but also scales beautifully with your growing user base. If you have any questions or feedback, feel free to drop a comment below!&lt;/p&gt;

&lt;p&gt;Happy coding! 💻✨&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>learning</category>
      <category>security</category>
    </item>
    <item>
      <title>🚀Clean Architecture in .NET</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Tue, 11 Feb 2025 15:51:35 +0000</pubDate>
      <link>https://dev.to/ipazooki/clean-architecture-in-net-1hid</link>
      <guid>https://dev.to/ipazooki/clean-architecture-in-net-1hid</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/C-BYhrlJiRU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Hello, fellow developers! 👋 Hope you’re doing well.  &lt;/p&gt;

&lt;p&gt;In the world of &lt;strong&gt;software architecture&lt;/strong&gt;, structuring our code properly is crucial for &lt;strong&gt;maintainability, scalability, and testability&lt;/strong&gt;. In our last discussion, we explored different architectural patterns, including &lt;strong&gt;Hexagonal Architecture, Layered Architecture, Onion Architecture&lt;/strong&gt;, and &lt;strong&gt;Clean Architecture&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Today, we’re going to &lt;strong&gt;dive deep into Clean Architecture&lt;/strong&gt;, what it is, why it matters, and how we can implement it in a real-world &lt;strong&gt;.NET&lt;/strong&gt; application. 💡  &lt;/p&gt;

&lt;p&gt;The full source code is available on &lt;a href="https://github.com/iPazooki/CleanArchitecture" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, so feel free to download it and follow along! 🛠️  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What is Clean Architecture?&lt;/strong&gt; 🤔
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Clean Architecture&lt;/strong&gt;, introduced by &lt;strong&gt;Uncle Bob (Robert C. Martin)&lt;/strong&gt;, is a &lt;strong&gt;domain-centric&lt;/strong&gt; approach that helps us build software that is:  &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Flexible&lt;/strong&gt; – Easy to swap technologies (databases, frameworks, UI).&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Maintainable&lt;/strong&gt; – Well-structured code with minimal dependencies.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Testable&lt;/strong&gt; – Business logic remains independent of external concerns.  &lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Layer Breakdown 🏗️&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Clean Architecture is structured into &lt;strong&gt;four key layers&lt;/strong&gt;:  &lt;/p&gt;

&lt;p&gt;1️⃣ &lt;strong&gt;Domain Layer (Core Business Logic)&lt;/strong&gt; – Contains entities, value objects, and domain services.&lt;br&gt;&lt;br&gt;
2️⃣ &lt;strong&gt;Application Layer (Use Cases &amp;amp; CQRS)&lt;/strong&gt; – Defines business rules, CQRS commands/queries, and interfaces.&lt;br&gt;&lt;br&gt;
3️⃣ &lt;strong&gt;Infrastructure Layer (Implementations &amp;amp; DB Access)&lt;/strong&gt; – Implements interfaces from the Application Layer.&lt;br&gt;&lt;br&gt;
4️⃣ &lt;strong&gt;Presentation Layer (API/UI Controllers)&lt;/strong&gt; – Exposes the functionality via endpoints or UI.  &lt;/p&gt;

&lt;p&gt;At its core, Clean Architecture follows the &lt;strong&gt;Dependency Rule&lt;/strong&gt;:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Inner layers should not depend on outer layers.&lt;/strong&gt;  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that &lt;strong&gt;business logic is independent&lt;/strong&gt; of frameworks, databases, and UI technologies. 🚀  &lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Implementing Clean Architecture in .NET&lt;/strong&gt; 🏗️
&lt;/h2&gt;

&lt;p&gt;Let’s break down how we’ve structured a &lt;strong&gt;.NET application&lt;/strong&gt; using Clean Architecture principles.  &lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;1️⃣ Domain Layer – The Heart of Clean Architecture&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Domain Layer&lt;/strong&gt; is the &lt;strong&gt;most important part&lt;/strong&gt; of Clean Architecture. It contains:  &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Entities&lt;/strong&gt; (&lt;code&gt;Book&lt;/code&gt;, &lt;code&gt;Person&lt;/code&gt;) – Define core data structures.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Value Objects&lt;/strong&gt; (&lt;code&gt;Genre&lt;/code&gt;, &lt;code&gt;Address&lt;/code&gt;) – Immutable objects with validation.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;BaseEntity &amp;amp; BaseAuditableEntity&lt;/strong&gt; – Common properties like &lt;code&gt;Id&lt;/code&gt;, &lt;code&gt;CreatedDate&lt;/code&gt;, &lt;code&gt;UpdatedDate&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Domain Exceptions&lt;/strong&gt; (&lt;code&gt;UnsupportedGenreException&lt;/code&gt;) – Custom exceptions for domain-level errors.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Domain Events&lt;/strong&gt; – Handling important business events.  &lt;/p&gt;

&lt;p&gt;A key decision here:&lt;br&gt;&lt;br&gt;
📌 &lt;strong&gt;Using &lt;code&gt;DateTimeOffset&lt;/code&gt; instead of &lt;code&gt;DateTime&lt;/code&gt;&lt;/strong&gt; ensures &lt;strong&gt;proper time zone handling&lt;/strong&gt; across different regions.  &lt;/p&gt;

&lt;p&gt;👉 The &lt;strong&gt;Domain Layer has no dependencies&lt;/strong&gt; on external frameworks or databases, keeping it &lt;strong&gt;pure and reusable&lt;/strong&gt;.  &lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;2️⃣ Application Layer – Business Logic &amp;amp; CQRS&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Application Layer&lt;/strong&gt; is where we define &lt;strong&gt;use cases and business rules&lt;/strong&gt;. It consists of:  &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;CQRS (Command Query Responsibility Segregation)&lt;/strong&gt; – Separating read and write operations.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Interfaces&lt;/strong&gt; (e.g., &lt;code&gt;IEmailService&lt;/code&gt;) – Abstractions for external dependencies.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Command Handlers&lt;/strong&gt; (&lt;code&gt;CreateBookCommandHandler&lt;/code&gt;, &lt;code&gt;DeleteBookCommandHandler&lt;/code&gt;).&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Validation Rules&lt;/strong&gt; – Using &lt;strong&gt;FluentValidation&lt;/strong&gt; to enforce constraints.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Unit of Work Pattern&lt;/strong&gt; – Ensuring atomic transactions.  &lt;/p&gt;

&lt;p&gt;📌 &lt;strong&gt;Key concept&lt;/strong&gt;:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Commands&lt;/strong&gt; modify data (&lt;code&gt;CreateBookCommand&lt;/code&gt;, &lt;code&gt;UpdateBookCommand&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries&lt;/strong&gt; retrieve data (&lt;code&gt;GetBookByIdQuery&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Application Layer only contains interfaces&lt;/strong&gt;, keeping it decoupled from the infrastructure.
&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;3️⃣ Infrastructure Layer – Implementation Details&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Infrastructure Layer&lt;/strong&gt; provides &lt;strong&gt;concrete implementations&lt;/strong&gt; of the interfaces defined in the &lt;strong&gt;Application Layer&lt;/strong&gt;. It includes:  &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Entity Framework Core (EF Core) Configuration&lt;/strong&gt; – Setting up database persistence.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;ApplicationDbContext&lt;/strong&gt; – The actual database context.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Unit of Work Implementation&lt;/strong&gt; – Managing transactions efficiently.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Email Service Implementation&lt;/strong&gt; – Sending emails using external services.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Global Exception Handling&lt;/strong&gt; – Standardizing error responses.  &lt;/p&gt;

&lt;p&gt;📌 &lt;strong&gt;Infrastructure should depend on Application&lt;/strong&gt;, not the other way around. This keeps the &lt;strong&gt;core logic flexible&lt;/strong&gt; while allowing technology choices to change without affecting the domain.  &lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;4️⃣ Presentation Layer – Exposing APIs &amp;amp; Dependency Injection&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Presentation Layer&lt;/strong&gt; (CleanArchitecture.API) is the &lt;strong&gt;entry point&lt;/strong&gt; of the application, responsible for:  &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;API Controllers&lt;/strong&gt; – Handling requests and responses.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Swagger Documentation&lt;/strong&gt; – Interactive API documentation.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Dependency Injection (DI) Setup&lt;/strong&gt; – Registering services.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Global Exception Handling&lt;/strong&gt; – Formatting errors consistently.  &lt;/p&gt;

&lt;p&gt;📌 In &lt;code&gt;Program.cs&lt;/code&gt;, we wire everything together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddApplicationServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddInfrastructureServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPresentationServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures &lt;strong&gt;all dependencies are properly injected&lt;/strong&gt; across the application.  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Key Benefits of Clean Architecture&lt;/strong&gt; 🏆
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Separation of Concerns&lt;/strong&gt; – Code is modular and easier to maintain.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Scalability&lt;/strong&gt; – Easily swap out technologies without major refactoring.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Testability&lt;/strong&gt; – Business logic remains independent and can be tested in isolation.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Flexibility&lt;/strong&gt; – The application can evolve over time with minimal disruption.  &lt;/p&gt;

&lt;p&gt;By following this structure, we ensure our &lt;strong&gt;.NET applications&lt;/strong&gt; are &lt;strong&gt;robust, maintainable, and scalable&lt;/strong&gt;. 🚀  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Wrapping Up &amp;amp; Next Steps&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;That’s a complete walkthrough of &lt;strong&gt;Clean Architecture in .NET&lt;/strong&gt;! 🎉  &lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;What’s Next?&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔥 Download the &lt;strong&gt;source code&lt;/strong&gt; from my GitHub repo and try it yourself!
&lt;/li&gt;
&lt;li&gt;👍 If you found this helpful, &lt;strong&gt;give it a star&lt;/strong&gt; on GitHub!
&lt;/li&gt;
&lt;li&gt;📢 Have questions? &lt;strong&gt;Drop a comment below!&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🔔 &lt;strong&gt;Subscribe&lt;/strong&gt; for more &lt;strong&gt;.NET &amp;amp; Software Architecture&lt;/strong&gt; content!
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading, and happy coding! 🎯  &lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>dotnet</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Blue-Green Deployment with Azure</title>
      <dc:creator>Mo</dc:creator>
      <pubDate>Wed, 22 Jan 2025 07:16:54 +0000</pubDate>
      <link>https://dev.to/ipazooki/blue-green-deployment-with-azure-2g42</link>
      <guid>https://dev.to/ipazooki/blue-green-deployment-with-azure-2g42</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/EtpCEs2_xtM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Hello, everyone! In this article, we’re diving into &lt;strong&gt;Blue-Green Deployment&lt;/strong&gt;, a brilliant technique for rolling out updates without breaking a sweat—or your app.  &lt;/p&gt;

&lt;p&gt;Here’s what we’ll cover:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What is Blue-Green Deployment?&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Pros and Cons&lt;/strong&gt;—because no approach is perfect.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When Should You Use It?&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step-by-Step Demo with Azure&lt;/strong&gt;—complete with automation tips to make your life easier.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, grab a cuppa ☕ and let’s get started! 🚀  &lt;/p&gt;




&lt;h2&gt;
  
  
  What is Blue-Green Deployment? 🤔
&lt;/h2&gt;

&lt;p&gt;Imagine you’ve got two versions of your application running—one is "Blue" (your live environment), and the other is "Green" (your shiny new version).  &lt;/p&gt;

&lt;p&gt;Instead of directly updating the live version and risking downtime, you deploy changes to the Green environment first. Once you’re sure everything’s working perfectly, you simply swap traffic from Blue to Green. Users won’t notice a thing—except the new features! 🎉  &lt;/p&gt;

&lt;p&gt;Think of it like swapping stage sets in a play between scenes—seamless and smooth. 🎭  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Pros and Cons 🛠️
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pros:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero Downtime&lt;/strong&gt;: No 'under maintenance' messages—just uninterrupted service.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Rollbacks&lt;/strong&gt;: Found a bug? Flip back to Blue and fix it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-like Testing&lt;/strong&gt;: Test your new version in a real-world environment.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better User Experience&lt;/strong&gt;: Smooth updates = happy users.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cons:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Higher Costs&lt;/strong&gt;: You’re running two environments, which can double infrastructure costs.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex Setup&lt;/strong&gt;: Managing two environments isn’t simple—it requires planning and good tools.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Synchronisation&lt;/strong&gt;: Handling databases and real-time data can get tricky.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing Overheads&lt;/strong&gt;: Blue and Green must be identical apart from the new features.
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  When Should You Use It? 🎯
&lt;/h2&gt;

&lt;p&gt;Blue-Green Deployment shines when downtime isn’t an option—think e-commerce sites, banking systems, or services that need constant availability.  &lt;/p&gt;

&lt;p&gt;If you’re working on a smaller project or have a tight budget, the added complexity and cost might not be worth it.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Demo with Azure 🖥️
&lt;/h2&gt;

&lt;p&gt;Let’s put theory into practice! I’ll guide you through setting up Blue-Green Deployment using Azure.  &lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: Create a Web App 🌐
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Set up a &lt;strong&gt;resource group&lt;/strong&gt; called &lt;code&gt;BlueGreenResource&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;web app&lt;/strong&gt; named &lt;code&gt;BlueGreenWebApp&lt;/code&gt;. Use &lt;code&gt;.NET 8&lt;/code&gt;, deploy to &lt;em&gt;UK South&lt;/em&gt;, and choose the &lt;em&gt;Standard S1&lt;/em&gt; pricing tier.
&lt;/li&gt;
&lt;li&gt;Disable &lt;strong&gt;App Insights&lt;/strong&gt; (optional for simple setups).
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Step 2: Publish the First Version ✅
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use Visual Studio to deploy your app to the live environment.
&lt;/li&gt;
&lt;li&gt;This is &lt;strong&gt;version 1&lt;/strong&gt;, which users can now see. 🎉
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 3: Add a Staging Slot 🛠️
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add a &lt;strong&gt;staging slot&lt;/strong&gt; to your web app. It’ll have its own domain (e.g., &lt;code&gt;staging.bluegreenwebapp.azurewebsites.net&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;Publish &lt;strong&gt;version 2&lt;/strong&gt; to this staging slot. Users won’t see this version yet, but it’s ready for testing.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 4: Swap the Slots 🔄
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Once QA approves, use the &lt;strong&gt;Swap&lt;/strong&gt; button in Azure to swap the staging slot with the live slot. Now, users are on &lt;strong&gt;version 2&lt;/strong&gt;! 🎊
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 5: Clean Up 🧹
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Delete the staging slot to save costs.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Automate the Process with Azure DevOps 🤖
&lt;/h3&gt;

&lt;p&gt;To avoid manual effort, we’ll set up a CI/CD pipeline in Azure DevOps:  &lt;/p&gt;

&lt;h4&gt;
  
  
  Build Pipeline (YAML File):
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;windows-latest'&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;solution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;**/*.sln'&lt;/span&gt;
  &lt;span class="na"&gt;buildPlatform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Any&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CPU'&lt;/span&gt;
  &lt;span class="na"&gt;buildConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Release'&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NuGetToolInstaller@1&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NuGetCommand@2&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;restoreSolution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(solution)'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DotNetCoreCLI@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;**/*.csproj'&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--configuration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(buildConfiguration)'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DotNetCoreCLI@2&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;publish&lt;/span&gt;
    &lt;span class="na"&gt;publishWebProjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--configuration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(BuildConfiguration)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--output&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(Build.ArtifactStagingDirectory)'&lt;/span&gt;
    &lt;span class="na"&gt;zipAfterPublish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishPipelineArtifact@1&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;targetPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.ArtifactStagingDirectory)'&lt;/span&gt; 
    &lt;span class="na"&gt;artifactName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deploymentapp-artifact'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pipeline triggers on changes to the &lt;code&gt;master&lt;/code&gt; branch. It builds the app, restores NuGet packages, and creates deployment artifacts.  &lt;/p&gt;




&lt;h4&gt;
  
  
  Release Pipeline:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a Staging Slot&lt;/strong&gt;:
Use this script:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   az webapp deployment slot create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;webAppName&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;resourceGroupName&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;slotName&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deploy to Staging&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
Set the staging slot as the target in the pipeline.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Swap Slots&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
Use the &lt;strong&gt;Azure App Service Manage&lt;/strong&gt; task to swap environments.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delete Staging Slot&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
Clean up with this script:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   az webapp deployment slot delete &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;webAppName&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;resourceGroupName&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;slotName&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Testing the Setup 🚀
&lt;/h3&gt;

&lt;p&gt;Push changes to the &lt;code&gt;master&lt;/code&gt; branch, and watch the magic happen:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build triggers automatically.
&lt;/li&gt;
&lt;li&gt;Deployment to the staging slot occurs.
&lt;/li&gt;
&lt;li&gt;QA approves the changes.
&lt;/li&gt;
&lt;li&gt;Slots are swapped, and the staging slot is deleted.
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Thoughts 💡
&lt;/h2&gt;

&lt;p&gt;Blue-Green Deployment makes updates safer, faster, and smoother, especially for critical systems. While it’s not without challenges, automation tools like Azure DevOps make it easier to manage.  &lt;/p&gt;

&lt;p&gt;Have you tried Blue-Green Deployment? Share your experiences in the comments! 👇 Don’t forget to like, share, and bookmark this article for future reference. 😊  &lt;/p&gt;

</description>
      <category>azure</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
