<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.0.1">Jekyll</generator><link href="https://blog.burgyn.online/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.burgyn.online/" rel="alternate" type="text/html" /><updated>2026-03-11T11:56:30+00:00</updated><id>https://blog.burgyn.online/feed.xml</id><title type="html">Burgyn’s blog</title><subtitle>A blog about everything possible and impossible that dotnet developer face every day.</subtitle><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><entry><title type="html">MMLib.DummyApi - Configurable mock API for prototypes and testing</title><link href="https://blog.burgyn.online/2026/03/04/mmlib-dummyapi" rel="alternate" type="text/html" title="MMLib.DummyApi - Configurable mock API for prototypes and testing" /><published>2026-03-04T17:00:00+00:00</published><updated>2026-03-04T17:00:00+00:00</updated><id>https://blog.burgyn.online/2026/03/04/mmlib-dummyapi</id><content type="html" xml:base="https://blog.burgyn.online/2026/03/04/mmlib-dummyapi"><![CDATA[<p>This is probably one of those projects that helps mostly me. Still, I wanted to share it in case you run into the same problems.</p>

<p>I often need a working API for quick UI prototypes, UI framework testing, integration tool testing, load-testing tools, and demo projects for talks <em>(conference, school, meetup)</em>. I do not want to build a new backend every single time just to have realistic endpoints and data.</p>

<p>So I built <a href="https://github.com/Burgyn/MMLib.DummyApi"><code class="highlighter-rouge">MMLib.DummyApi</code></a>: a configurable mock REST API that starts fast and gives me fully functional CRUD endpoints, generated seed data, validation, and edge-case simulation.</p>

<h2 id="why-this-project-exists">Why this project exists</h2>

<p>The core idea is simple:</p>

<ul>
  <li>I have different frontends and tools to test.</li>
  <li>I need realistic data and behavior.</li>
  <li>I need to control “bad” scenarios too.</li>
</ul>

<p>Instead of creating another one-off API <em>(or asking an agent to generate one again)</em>, I provide configuration and the API is ready.</p>

<p>Out of the box, it is not read-only mock data. The API works with:</p>

<ul>
  <li><code class="highlighter-rouge">GET /{collection}</code></li>
  <li><code class="highlighter-rouge">GET /{collection}/{id}</code></li>
  <li><code class="highlighter-rouge">POST /{collection}</code></li>
  <li><code class="highlighter-rouge">PUT /{collection}/{id}</code></li>
  <li><code class="highlighter-rouge">DELETE /{collection}/{id}</code></li>
</ul>

<p>POST/PUT payloads are validated by your JSON schema, and collections can be seeded with dummy data on startup.</p>

<h2 id="quick-start">Quick start</h2>

<p>Run it with Docker:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull ghcr.io/burgyn/mmlib-dummyapi
docker run <span class="nt">-p</span> 8080:8080 ghcr.io/burgyn/mmlib-dummyapi
</code></pre></div></div>

<p>Try a default collection:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:8080/products
</code></pre></div></div>

<p>API docs are available at <code class="highlighter-rouge">http://localhost:8080/scalar/v1</code>.</p>

<h2 id="create-your-own-records-collection">Create your own records collection</h2>

<p>In my case, I often need some custom “evidence” or “records” collection. Here is a minimal example:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"collections"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"records"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Records"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"seedCount"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
      </span><span class="nl">"schema"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"object"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"required"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"title"</span><span class="p">,</span><span class="w"> </span><span class="s2">"status"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w"> </span><span class="nl">"minLength"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">},</span><span class="w">
          </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w"> </span><span class="nl">"enum"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"new"</span><span class="p">,</span><span class="w"> </span><span class="s2">"processing"</span><span class="p">,</span><span class="w"> </span><span class="s2">"done"</span><span class="p">]</span><span class="w"> </span><span class="p">},</span><span class="w">
          </span><span class="nl">"ownerEmail"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w"> </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="s2">"email"</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Save it as <code class="highlighter-rouge">my-collections.json</code> and start the container with config mount:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-p</span> 8080:8080 <span class="se">\</span>
  <span class="nt">-v</span> ./my-collections.json:/config/collections.json <span class="se">\</span>
  <span class="nt">-e</span> <span class="nv">DUMMYAPI__COLLECTIONSFILE</span><span class="o">=</span>/config/collections.json <span class="se">\</span>
  ghcr.io/burgyn/mmlib-dummyapi
</code></pre></div></div>

<p>Now verify round-trip CRUD quickly:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:8080/records <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"title":"Load test scenario","status":"new","ownerEmail":"john@example.com"}'</span>

curl http://localhost:8080/records
</code></pre></div></div>

<h2 id="simulate-edge-cases-without-extra-coding">Simulate edge cases without extra coding</h2>

<p>This is the part I use a lot in demos and testing:</p>

<ul>
  <li>Delays: <code class="highlighter-rouge">X-Simulate-Delay: 500</code></li>
  <li>Forced error: <code class="highlighter-rouge">X-Simulate-Error: true</code></li>
  <li>Retry scenario: <code class="highlighter-rouge">X-Simulate-Retry: 3</code> with <code class="highlighter-rouge">X-Request-Id</code></li>
  <li>Random chaos latency/failures with chaos headers</li>
  <li>Background updates with <code class="highlighter-rouge">backgroundJob</code> <em>(for example status transitions)</em></li>
</ul>

<p>A tiny retry simulation call can look like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:8080/records <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"X-Simulate-Retry: 3"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"X-Request-Id: demo-retry-1"</span>
</code></pre></div></div>

<p>This lets me test how UI and integration clients behave when things are not perfect, without implementing custom fault endpoints.</p>

<h2 id="wrap-up">Wrap-up</h2>

<p>I built this as a practical utility for my own daily workflows, but maybe it helps you too if you prototype often or need controllable API behavior for tests and demos.</p>

<p>I kept this article intentionally simple. Full details, all configuration options, and more advanced scenarios are in the project README.</p>

<h2 id="links">Links</h2>

<ul>
  <li>GitHub repository: <a href="https://github.com/Burgyn/MMLib.DummyApi">https://github.com/Burgyn/MMLib.DummyApi</a></li>
</ul>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="dotnet" /><category term="api" /><category term="testing" /><category term="tools" /><summary type="html"><![CDATA[I built a configurable mock API with CRUD and edge-case simulation for UI prototypes, demos, and testing tools.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/mmlib-dummyapi-cover.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/mmlib-dummyapi-cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Built-in validation for Minimal APIs in .NET 10</title><link href="https://blog.burgyn.online/2026/02/11/minimal-api-validation" rel="alternate" type="text/html" title="Built-in validation for Minimal APIs in .NET 10" /><published>2026-02-11T17:00:00+00:00</published><updated>2026-02-11T17:00:00+00:00</updated><id>https://blog.burgyn.online/2026/02/11/minimal-api-validation</id><content type="html" xml:base="https://blog.burgyn.online/2026/02/11/minimal-api-validation"><![CDATA[<p>Minimal APIs have been around since .NET 6 and have evolved a lot, but one thing was missing: first-class validation support. With .NET 10 <em>(since November 2025)</em>, that gap is finally closed thanks to revived <code class="highlighter-rouge">System.ComponentModel.DataAnnotations</code> support for Minimal APIs.</p>

<h2 id="how-it-works">How it works</h2>

<p>You need three things:</p>

<ol>
  <li><strong>Register validation in DI</strong> - call <code class="highlighter-rouge">builder.Services.AddValidation();</code> in <code class="highlighter-rouge">Program.cs</code>.</li>
  <li><strong>Decorate your request types</strong> with DataAnnotation attributes: <code class="highlighter-rouge">[Required]</code>, <code class="highlighter-rouge">[MinLength]</code>, <code class="highlighter-rouge">[EmailAddress]</code>, <code class="highlighter-rouge">[Range]</code>, <code class="highlighter-rouge">[Compare]</code>, and so on.</li>
  <li><strong>Do nothing else</strong> - a source generator emits the validation logic; your endpoint signature stays the same.</li>
</ol>

<p>If validation fails, the framework returns 400 Bad Request with a <code class="highlighter-rouge">ProblemDetails</code> - style payload before your handler runs.</p>

<h2 id="where-you-can-use-it">Where you can use it</h2>

<p>This validation works for:</p>

<ul>
  <li><strong>Request body</strong> - e.g. JSON bound to a record or class.</li>
  <li><strong>Query parameters</strong> - e.g. <code class="highlighter-rouge">[FromQuery]</code> with attributes.</li>
  <li><strong>Headers</strong> - when bound from headers with the right attributes.</li>
</ul>

<p>So you get the same declarative style you know from MVC, but without the controller layer.</p>

<h2 id="important-the-request-type-must-be-public">Important: the request type must be public</h2>

<p>If your request class or record is not <strong>public</strong>, validation will not run. The generated code only sees public types. Keep that in mind when you define DTOs in the same file as your endpoints or in a separate assembly.</p>

<h2 id="example">Example</h2>

<p>Let’s put it together. First, add the validation services and define a minimal endpoint:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.ComponentModel.DataAnnotations</span><span class="p">;</span>

<span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="n">WebApplication</span><span class="p">.</span><span class="nf">CreateBuilder</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>

<span class="c1">// 👇 Add validation support</span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddValidation</span><span class="p">();</span>

<span class="kt">var</span> <span class="n">app</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="nf">Build</span><span class="p">();</span>

<span class="c1">// 👇 Nothing changed here in your endpoint</span>
<span class="n">app</span><span class="p">.</span><span class="nf">MapPost</span><span class="p">(</span><span class="s">"/users"</span><span class="p">,</span> <span class="p">(</span><span class="n">CreateUserRequest</span> <span class="n">request</span><span class="p">)</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">Results</span><span class="p">.</span><span class="nf">Ok</span><span class="p">(</span><span class="k">new</span> <span class="p">{</span> <span class="n">id</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">Email</span> <span class="p">});</span>
<span class="p">});</span>

<span class="n">app</span><span class="p">.</span><span class="nf">Run</span><span class="p">();</span>
</code></pre></div></div>

<p>Your handler still receives a <code class="highlighter-rouge">CreateUserRequest</code>; the framework validates it before the lambda runs. The request type is a <strong>public</strong> record with DataAnnotations:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 👇 Your model class with validation attributes</span>
<span class="k">public</span> <span class="n">record</span> <span class="nf">CreateUserRequest</span><span class="p">(</span>
    <span class="p">[</span><span class="n">Required</span><span class="p">,</span> <span class="nf">MinLength</span><span class="p">(</span><span class="m">2</span><span class="p">),</span> <span class="nf">MaxLength</span><span class="p">(</span><span class="m">100</span><span class="p">)]</span>
    <span class="kt">string</span> <span class="n">Name</span><span class="p">,</span>
    <span class="p">[</span><span class="n">Required</span><span class="p">,</span> <span class="n">EmailAddress</span><span class="p">]</span>
    <span class="kt">string</span> <span class="n">Email</span><span class="p">,</span>
    <span class="p">[</span><span class="n">Required</span><span class="p">,</span> <span class="nf">MinLength</span><span class="p">(</span><span class="m">8</span><span class="p">),</span> <span class="nf">MaxLength</span><span class="p">(</span><span class="m">100</span><span class="p">)]</span>
    <span class="kt">string</span> <span class="n">Password</span><span class="p">,</span>
    <span class="p">[</span><span class="n">property</span><span class="p">:</span> <span class="n">Required</span><span class="p">,</span> <span class="nf">Compare</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">CreateUserRequest</span><span class="p">.</span><span class="n">Password</span><span class="p">))]</span>
    <span class="kt">string</span> <span class="n">ConfirmPassword</span><span class="p">,</span>
    <span class="p">[</span><span class="nf">Range</span><span class="p">(</span><span class="m">18</span><span class="p">,</span> <span class="m">120</span><span class="p">)]</span> <span class="kt">int</span> <span class="n">Age</span><span class="p">);</span>
</code></pre></div></div>

<p>For records, note the <code class="highlighter-rouge">property:</code> prefix on <code class="highlighter-rouge">[Compare]</code> so the attribute is applied to the generated property. Once that’s in place, invalid requests (e.g. short name, bad email, mismatched passwords, or age out of range) get a 400 response automatically.</p>

<h2 id="when-you-need-more-than-dataannotations">When you need more than DataAnnotations</h2>

<p>DataAnnotations are great for straightforward rules. If you need more complex, FluentValidation-style logic <em>(cross-field rules, async validators, or heavy reuse)</em>, you can still use custom endpoint filters - I wrote about that approach in an earlier post <em>(in Slovak)</em>: <a href="/2023/01/16/asp-net-core-minimal-api-filters">ASP.NET Core Minimal API – Filters &amp; Validation</a>. The built-in validation in .NET 10 simply gives you a zero-friction option when attributes are enough.</p>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="C#" /><category term=".NET" /><category term="ASP.NET CORE" /><category term="Minimal API" /><summary type="html"><![CDATA[Built-in validation for Minimal APIs in .NET 10: AddValidation(), DataAnnotations, and what to watch for.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/blog-post-base-cover.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/blog-post-base-cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">My Reading and Study Workflow for Tech Articles</title><link href="https://blog.burgyn.online/2026/02/05/my-reading-and-study-workflow-for-tech-articles" rel="alternate" type="text/html" title="My Reading and Study Workflow for Tech Articles" /><published>2026-02-05T17:00:00+00:00</published><updated>2026-02-05T17:00:00+00:00</updated><id>https://blog.burgyn.online/2026/02/05/my-reading-and-study-workflow-for-tech-articles</id><content type="html" xml:base="https://blog.burgyn.online/2026/02/05/my-reading-and-study-workflow-for-tech-articles"><![CDATA[<p>I thought I’d share my flow for collecting and reading articles. Maybe you’ll find it useful, or maybe you’ll share yours in the comments.</p>

<p>🔍 Where I find articles
I usually discover interesting content in 4 places:</p>

<p>📬 <a href="https://vlko.substack.com/">Marián Vlčák aka vlko’s monthly newsletter</a>
Consistently high-quality tech content.</p>

<p>📰 Medium newsletter - Though lately it’s been flooded with “5 things every .NET developer must know” type articles. You have to dig deeper to find the gems.</p>

<p>💼 LinkedIn - Still a solid source for technical articles. After Twitter’s decline, it became even more valuable.</p>

<p>☁️ BlueSky - For me, the best Twitter replacement. I’ve created a custom feed built as a filter over my list of specific people I want to follow. BlueSky’s killer feature is the ability to create your own feeds and have full control over what you see and how much.
<a href="https://bsky.app/profile/did:plc:gdx2f7prj422nixph6laigs7/lists/3mc5jltbaks2n">My Developers list.</a></p>

<p>🎙️ Podcasts - Another source where I often discover interesting topics to explore further.</p>

<p>📚 My reading workflow
When I find an interesting article, I don’t read it immediately. Instead, I bookmark it using raindrop.io - my go-to bookmarking system for everything.
Articles I want to read go into a “Blogs” folder, which I’ve set up as a public RSS feed. This RSS feed is connected to Instapaper - a reader that creates a clean copy of the article, optimizes it for mobile reading, and most importantly: preserves scroll position. Whenever I return to an article, I’m exactly where I left off.</p>

<p>🤔 Why this seemingly complex setup?
I separate two activities:
 ⚡ Discovery - scrolling feeds and finding new content
 📖 Deep reading - actually consuming the content</p>

<p>My BlueSky feed is limited to max 20 posts per day. I quickly scan what interests me and save it for later. When I’m in the mood for reading, I open Instapaper. Raindrop keeps all my bookmarks organized in one place.
What’s your workflow for managing technical articles? Do you read immediately or save for later?</p>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="productivity" /><category term="developerworkflow" /><category term="continuouslearning" /><summary type="html"><![CDATA[I thought I’d share my flow for collecting and reading articles. Maybe you’ll find it useful, or maybe you’ll share yours in the comments.]]></summary></entry><entry><title type="html">ASP.NET CORE Junior Developer Roadmap</title><link href="https://blog.burgyn.online/2024/10/29/asp-net-core-junior-developer-roadmap" rel="alternate" type="text/html" title="ASP.NET CORE Junior Developer Roadmap" /><published>2024-10-29T17:00:00+00:00</published><updated>2024-10-29T17:00:00+00:00</updated><id>https://blog.burgyn.online/2024/10/29/asp-net-core-junior-developer-roadmap</id><content type="html" xml:base="https://blog.burgyn.online/2024/10/29/asp-net-core-junior-developer-roadmap"><![CDATA[<h2 id="úvod">Úvod</h2>

<p>Občas sa ma ľudia pýtajú: „Učím sa programovať a chcel by som sa stať backend vývojárom. Ako začať?” alebo „Som junior/medior vývojár a chcem sa posunúť ďalej. Na čo sa mám zamerať?” Ak ide o niekoho z môjho okolia, vieme si sadnúť a prebrať to. Postupne sa dopracujeme k vhodnej ceste. Keď sa ma však opýta niekto na LinkedIn, odpoveď býva: „Je to špecifické, záleží na tom, čo presne chceš alebo potrebuješ…” A tam to väčšinou končí. Prípadne mu ešte odporúčim veľmi dobre spracovaný zoznam oblastí a zdrojov pre vývoj v prostredí .NET od <a href="https://medium.com/@techworldwithmilan/net-developer-roadmap-2023-c1a9a102748e">Milana Milanoviča (čistá zhoda mien 😊)</a>.</p>

<p>Preto som sa rozhodol napísať túto mini sériu článkov, kde popíšem, ako môže vyzerať cesta od junior ASP.NET CORE backend vývojára po seniora. Nejde o návod na programovanie, ale skôr o opis cesty, ktorou sa vývojár môže vydať. Vysvetlím, v ktorej fáze je dobré ovládať určité zručnosti a aké vedomosti by mal mať.</p>

<blockquote>
  <p>💭 Toto nie je žiadna rigorózna práca. Každý človek je iný, každá firma má odlišné očakávania a potreby. Môžeš to brať ako jednu z možností, ktorou sa môžeš inšpirovať.</p>
</blockquote>

<h2 id="mojich-5-odporúčaní-pre-budúcich-vývojárov">Mojich 5 odporúčaní pre budúcich vývojárov</h2>

<p>Tu je mojich 5 všeobecných odporúčaní, ktoré podľa mňa môžu pomôcť na ceste k tomu, aby sa z teba stal lepší vývojár.</p>

<h3 id="1️⃣-práca-ťa-musí-baviť">1️⃣ Práca ťa musí baviť</h3>

<p>Nezáleží na tom, či práve začínaš a vymýšľaš si projekt, na ktorom sa budeš učiť, či máš nápad, ktorý chceš zrealizovať, alebo hľadáš novú prácu. Vyberaj si tak, aby ťa to bavilo, aby si v tom videl zmysel a aby si videl výsledky. Tieto tri faktory (niekedy potrebuješ všetky tri, inokedy stačí jeden z nich) ťa môžu dostatočne motivovať, aby si sa zlepšoval, rástol a zotrval.</p>

<h3 id="2️⃣-chuť-a-vytrvalosť">2️⃣ Chuť a vytrvalosť</h3>

<p>Z vlastnej skúsenosti so začínajúcimi vývojármi viem, že chuť učiť sa, tvoriť a vytrvalosť sú dôležitejšie než momentálne vedomosti a schopnosti.</p>

<h3 id="3️⃣-zvedavosť">3️⃣ Zvedavosť</h3>

<p>Zvedavosť a záujem o to, ako veci fungujú, vedomé vnímanie toho, s čím pracuješ, môžu výrazne prispieť k tvorbe lepších riešení. Keď si všímam aplikácie, ktoré používam (čo dokážu, čo na nich oceňujem a čo mi nevyhovuje), môžem zákazníkom priniesť lepší produkt. Ak si uvedomujem, ako fungujú knižnice a frameworky, ktoré využívam, môžem navrhnúť efektívnejší kód.</p>

<h3 id="4️⃣-lenivosť">4️⃣ Lenivosť</h3>

<p>Tento bod možno niekoho prekvapí a na prvý pohľad si môže odporovať s predchádzajúcimi. Vysvetlím. Ak mi po tretíkrát príde rovnaká požiadavka, mal by sa prejaviť môj zmysel pre „lenivosť” a mal by som si danú úlohu zautomatizovať. Ak niekto opakovane potrebuje rovnaký report, vytvorím na to skript. Ak často zakladám nový projekt, pripravím si scaffold. Lenivý vývojár = efektívny vývojár 😊. Lenivosť je často motorom inovácií.</p>

<h3 id="5️⃣-vystúp-zo-svojej-komfortnej-zóny">5️⃣ Vystúp zo svojej komfortnej zóny</h3>

<p>Opustenie komfortnej zóny nás dokáže posunúť výrazne dopredu. Komunikuj s kolegami, nauč sa používať netechnický jazyk, vysvetľuj ľuďom, čo robíš, ich vlastnými slovami. Neboj sa komunikovať so zákazníkmi ani zdieľať svoje vedomosti v komunite. Uč sa soft skills. Nemusíš všetko zvládnuť hneď, ale zamysli sa, kde chceš byť v budúcnosti a vyber si to, čo ťa tam posunie. Práve toto sú veci, ktoré sú v súčasnosti tie rozdielové.</p>

<h2 id="junior--medior--senior">Junior / Medior / Senior</h2>

<p>V IT firmách sa zaužívalo rozdelenie na junior, medior a senior vývojárov z pohľadu kariérneho rastu alebo zručností. Neexistuje jednotná definícia toho, kto je junior, medior a senior, pretože každá firma to vníma po svojom. Všeobecne však platí, že tieto úrovne kombinujú odpracované roky, technické schopnosti, samostatnosť, zodpovednosť a soft skills.</p>

<h2 id="junior-vývojár">Junior vývojár</h2>

<p>Poďme sa pozrieť na jednu z možných ciest, ako sa môže junior programátor postupne dostať do vývoja ASP.NET CORE REST API.</p>

<h3 id="prvý-projekt">Prvý projekt</h3>

<p>🚩 Existuje mnoho roadmap a odporúčaní, čo všetko by mal takýto vývojár vedieť. Zvyčajne začínajú tým, že by mal ovládať základy C# a .NET. Ja by som sa však pridržiaval môjho prvého odporúčania: práca ťa musí baviť a musíš vidieť výsledky. Študovať C# je dôležité, ale výsledky prídu až časom.</p>

<p>Preto by som rovno začal s ASP.NET CORE Minimal API a vyskúšal si prvé endpointy.</p>

<p>Takto veľmi rýchlo uvidíš výsledok svojej práce. Máš prvé endpointy, ktoré si môžeš otestovať a postupne si začneš tvoriť obraz o tom, čo daná technológia umožňuje a na čo je dobrá.</p>

<p>Navrhni si svoj prvý jednoduchý projekt a postupne skúšaj, čo do neho dokážeš zapracovať (napr. zoznam kontaktov, nákupný zoznam, receptár atď.).</p>

<p>V tejto fáze si to zjednoduš a dáta nikam neukladaj, maximálne ich drž v pamäti. Na prácu s databázou príde čas.</p>

<p>🚩 Keď už máš prvé endpointy za sebou, vráť sa k C# a preštuduj si základy. Premenné, základné dátové typy, funkcie, metódy a jednoduché triedy ti umožnia postupne riešiť komplexnejšie úlohy.</p>

<p>🚩 Prvé requesty si pravdepodobne vykonal priamo z prehliadača pomocou API dokumentácie. Aby si mohol plnohodnotne pracovať s API, budeš si musieť osvojiť prácu s niektorým z HTTP klientov. Najpoužívanejší je Postman, ale môžeš sa stretnúť aj s Insomnia či inými nástrojmi.</p>

<p>🚩 Dáta sú základom väčšiny projektov. Potrebuješ ich ukladať, manipulovať s nimi a poskytovať klientom. Možnosti na ukladanie dát sú rôzne, ale v prípade štruktúrovaných dát sa väčšinou jedná o SQL alebo NoSQL databázy. V nových projektoch je podiel SQL a NoSQL databáz pomerne vyrovnaný, v starších projektoch však častejšie narazíš na SQL databázy. Pre začiatok odporúčam SQL, keďže pravdepodobnosť, že s ním budeš pracovať, je vysoká.</p>

<p>Na úvod to nemusí byť nič zložité. Skús začať s Entity Frameworkom, pripojiť sa na existujúcu databázu, vytvoriť prvú migráciu a naučiť sa, ako načítať dáta.</p>

<p>🚩 Aby si sa mohol ďalej posúvať, potrebuješ ísť hlbšie do ASP.NET Core. Postupne si rozšír svoje zadanie. Pridaj nové sady endpointov, aby si pochopil, ako funguje routing. Preštuduj si model binding, aby si vedel, akými spôsobmi môžeš posielať dáta na backend (cez <code class="highlighter-rouge">path</code>, <code class="highlighter-rouge">query params</code>, <code class="highlighter-rouge">headers</code>, <code class="highlighter-rouge">body</code>).</p>

<p>Dáta, ktoré ti prichádzajú, potrebuješ validovať, aby si mal istotu, že dostávaš to, čo očakávaš. Odporúčam sa rovno pozrieť na FluentValidation, keďže model validation princíp, ktorý sa používal v minulosti, už v MinimalAPI nie je možný.</p>

<p>Aby si mohol lepšie štruktúrovať kód, budeš potrebovať pochopiť základy dependency injection (DI). Skús presunúť prácu s dátami zo samotných endpointov do samostatnej triedy (to, či ju nazveš repository, store, manager, je v tejto fáze jedno). Túto triedu teraz nakonfiguruj do DI kontajnera a nechaj si ju injektnúť do tvojich endpointov. Snaž sa pochopiť, na čo je to dobré a aspoň rámcovo pochop, ako to funguje.</p>

<p>🫡 Ak zvládneš tieto veci, budeš pripravený pridať sa k tímu a začať pracovať na menších úlohách v reálnom projekte.</p>

<blockquote>
  <p>💁‍♂️ Odporúčam začať s MinimalAPI. Ak však vieš, že firma, v ktorej budeš pracovať (alebo pracuješ), používa klasický prístup cez controllery, prečítaj si o tom tiež.</p>

</blockquote>

<p>Toto je napríklad u nás nutné minimum, ktoré musí junior ovládať, aby mohol prísť k nám do firmy. Po pohovore dostane zadanie, ktoré, ak vypracuje, tak by mal mať tieto zručnosti zvládnuté.</p>

<p>✅ Checklist:</p>

<p><img src="/assets/images/junior-developer/r-1.png" alt="Checklist prvý projekt" /></p>

<p><strong>🔗 Zdroje</strong></p>

<ul>
  <li><a href="https://learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-8.0&amp;tabs=visual-studio">Create a web API with ASP.NET Core</a></li>
  <li><a href="https://www.codecademy.com/learn/learn-c-sharp">Learn C#</a></li>
  <li><a href="https://learn.microsoft.com/en-us/shows/c-fundamentals-for-absolute-beginners/">C# Fundamentals for Absolute Beginners</a></li>
  <li><a href="https://www.postman.com/product/what-is-postman/">What is Postman?</a></li>
  <li><a href="https://learn.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli">First EF Core App</a></li>
  <li><a href="https://docs.fluentvalidation.net/en/latest/">FluentValidation Documentation</a></li>
  <li><a href="https://blog.burgyn.online/2023/01/16/asp-net-core-minimal-api-filters">ASP.NET Core Minimal API Filters</a></li>
  <li><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0">Dependency injection in ASP.NET Core</a></li>
</ul>

<h3 id="práca-v-tíme">Práca v tíme</h3>

<p>Pokiaľ máš ako junior zvládnuté vyššie spomínané veci <em>(v rôznej úrovni. Niečo viac, niečo trochu menej)</em> môže začať robiť v tíme.</p>

<p>🚩 V tíme nie si sám. Na to aby tími dokázali rozumne pracovať nad spoločným code baseom využívajú rôzne verzionovacie systémy. V súčasnej dobe to je najčastejšie GIT. Je potrebné aby si zvládol základnú prácu s GIT-om. Na začiatok stačí vedieť získať aktuálnu verziu, vytvoriť si novú vetvu. Vedieť commitovať zmeny a pushnúť zmeny na server.</p>

<p>🚩 Nasleduje Pull request proces, ktorý má daná firma nastavený. <em>(Budeš musieť spoznať základy s nástrojom, ktorý používajú. GitHub / GitLab / AZURE DevOps / …)</em>  Tam získaš feadback na svoj kód. Je to výborné miesto na učenie sa. Pretože dostaneš konštruktívne pripomienky od skúsených ľudí. Naučíš sa veľa o projekte, na ktorom pracuješ, ale aj o samotnom programovaní.</p>

<p>🚩 Firma, v ktorej budeš pracovať, má určite nastavený systém práce, využívajú jednu z viacerých metód riadenia softvérového vývoja. V súčasnosti to bude pravdpodobne nejaká z agilných techník. Skús pochopiť základy agilného riadenia, pozri si čo je scrum.</p>

<p>🚩 Ďalej sa vzdelávaj v C# / .NET. Mal by si zvládnuť základy OOP v .NET (classes, objects, interfaces, inheritance, composition), základy generík, údajové štruktúry, error handling, … Je potrebné naučiť sa pracovať so štruktúrou projektov, závislosťami, balíčkovacím systémom (Nuget).</p>

<p>Na projekte už nepracuješ sám, potrebuješ pochopiť a dodržiavať dohodnuté tímové štandardy, čítaš a orientuješ sa v cudzom kóde.</p>

<p>✅ Checklist:</p>

<p><img src="/assets/images/junior-developer/r-2.png" alt="Praca v time.png" /></p>

<p><strong>🔗 Zdroje</strong></p>

<ul>
  <li><a href="https://medium.com/@dilkushi.j/scrum-for-beginners-ab1177f86f95">Scrum for Beginners</a></li>
  <li><a href="https://einsmanntech.medium.com/soft-skills-help-developers-level-up-here-are-the-10-id-tell-my-junior-self-e8daaa26ad8e">Soft Skills Help Developers Level Up: 10 Tips</a></li>
  <li><a href="https://dev.to/kanani_nirav/getting-started-with-github-actions-a-beginners-guide-og7">Getting Started with GitHub Actions: A Beginner’s Guide</a></li>
  <li><a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/create-first-pipeline?view=azure-devops&amp;tabs=java,browser">Create Your First Pipeline with Azure DevOps</a></li>
  <li><a href="https://github.blog/developer-skills/github/beginners-guide-to-github-creating-a-pull-request/">Beginner’s Guide to GitHub: Creating a Pull Request</a></li>
  <li><a href="https://www.w3schools.com/git/default.asp">Git Tutorial - W3Schools</a></li>
  <li><a href="https://dotnet.microsoft.com/en-us/learn/csharp">Learn C# with .NET</a></li>
</ul>

<h3 id="príprava-na-mediora">Príprava na Mediora</h3>

<p>Keď už zvládneš prácu v tíme a pracuješ na reálnych projektoch, začína sa obdobie, kedy sa pripravuješ na úroveň mediora. Medior vývojár by mal mať zvládnuté nielen technické zručnosti, ale aj schopnosť samostatne riešiť problémy, robiť rozhodnutia a prichádzať s návrhmi zlepšení.</p>

<p>🚩 Postupne budeš čoraz samostatnejší a budeš dostávať komplexnejšie úlohy. Tvoj kód musí byť nielen funkčný, ale aj prehľadný a udržiavateľný. V tejto fáze je dôležité osvojiť si princípy ako DRY (Don’t Repeat Yourself), KISS (Keep It Simple, Stupid) a YAGNI (You Aren’t Gonna Need It). Tieto základné princípi ti pomôžu ľahšie pristupovať k hľadaniu riešení.</p>

<p>🚩 V rámci ASP.NET Core budeš musieť hlbšie pochopiť základy <code class="highlighter-rouge">HTTP</code> protokolu, parameter bindingu, autentifikácie a autorizácie. Hoci sa neočakáva, že navrhneš celý proces autorizácie, mal by si rozumieť jej významu a základnému fungovaniu v projekte. Pre efektívnu prácu s dátami je dôležité pochopiť koncept DTO a data mappingu, kde ti môžu pomôcť knižnice ako AutoMapper alebo Mapster. Rovnako je dôležité oboznámiť sa s tým, ako sa v ASP.NET Core používa Options pattern na konfiguráciu služby.</p>

<blockquote>
  <p>📖 Nauč sa čítať dokumentáciu. Prejdi si dokumentáciu frameworku, s ktorým pracuješ.</p>
</blockquote>

<p>🚩 Práca s databázami je kľúčová. Mal by si zvládnuť nielen základné DML príkazy (<code class="highlighter-rouge">SELECT</code>, <code class="highlighter-rouge">INSERT</code>, <code class="highlighter-rouge">UPDATE</code>, <code class="highlighter-rouge">DELETE</code>), ale aj prácu s migráciami a vedieť, prečo a ako používať indexy na optimalizáciu dotazov. Technické fungovanie indexov môžeš zatiaľ považovať za mágiu 🔮.</p>

<p>🚩 Základné vývojárske zručnosti zahŕňajú efektívne používanie tvojho IDE (Visual Studio, VS Code, Rider), vrátane skratiek, debugovania a analýzy chýb. Investuj čas do spoznávania možností tvojho IDE, až kým niektoré skratky nebudeš mať v rukách namiesto v hlave. Tým získaš viac priestoru na premýšľanie o samotnom riešení a tvoje ruky budú len pretavovať tvoje myšlienky do kódu.</p>

<p>Rovnako by si mal byť zbehlý v práci s príkazovým riadkom, najmä s nástrojmi ako dotnet CLI a ef CLI, ktoré ti umožnia jednoducho spravovať projekty a databázy. UI sa neustále mení, ale CLI príkazy sú trvácne. Keď sa naučíš pracovať s CLI, tvoja produktivita výrazne vzrastie.</p>

<p>🚩 Základy DevOps ti pomôžu pochopiť, kde a ako je projekt nasadený a osvojíš si proces CI/CD (Continuous Integration / Continuous Deployment), aby si vedel lepšie spolupracovať s rôznymi časťami tímu.</p>

<p>🚩 Nezabúdaj na testovanie. Aj keď budete mať testerov, vývojár je spoluzodpovedný za kvalitu svojho kódu. Mal by si rozumieť tomu, čo vyvíjaš a vedieť otestovať základné scenáre.</p>

<p>Postupne si osvoj aj automatické testovanie. Začni s písaním unit testov a zdokonaľuj sa v nich. Ak sa na projekte používajú aj iné typy testov, ako napríklad E2E, integračné alebo load testy, na začiatku ich budeš vedieť upravovať (keď tvoja zmena spôsobí, že test zlyhá) a neskôr ich budeš sám vytvárať.</p>

<blockquote>
  <p>💁 Pri všetkej svojej práci nezabúdaj, že máš okolo seba šikovných kolegov, ktorí ti radi pomôžu, poradia a nasmerujú ťa. Využi ich skúsenosti a vedomosti. Na druhej strane, pamätaj, že práve tí najšikovnejší bývajú často veľmi vyťažení. Preto si vyberaj správny prístup, správne slová, a maj pokoru, no aj sebadôveru. Buď trpezlivý, keď odpoveď nepríde hneď.</p>
</blockquote>

<p>✅ Checklist</p>

<p><img src="/assets/images/junior-developer/r-3.png" alt="Príprava na mediora - Develoepr roadmap.png" /></p>

<p><strong>🔗 Zdroje</strong></p>

<ul>
  <li><a href="https://learn.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-8.0">Introduction to ASP.NET Core</a></li>
  <li><a href="https://workat.tech/machine-coding/tutorial/software-design-principles-dry-yagni-eytrxfhz1fla">Software Design Principles: DRY, YAGNI</a></li>
  <li><a href="https://www.boldare.com/blog/kiss-yagni-dry-principles/">KISS, YAGNI, and DRY Principles</a></li>
  <li><a href="https://learn.microsoft.com/en-us/ef/">Entity Framework Documentation</a></li>
  <li><a href="https://toxigon.com/microsoft-visual-studio-tips-and-tricks">Microsoft Visual Studio Tips and Tricks</a></li>
  <li><a href="https://learnwithinderjeet.com/blog/mastering-visual-studio-2022-shortcuts-tips-and-tricks/">Mastering Visual Studio 2022: Shortcuts, Tips, and Tricks</a></li>
  <li><a href="https://www.meziantou.net/quick-introduction-to-xunitdotnet.htm">Quick Introduction to xUnit</a></li>
  <li><a href="https://fluentassertions.com/">FluentAssertions Documentation</a></li>
  <li><a href="https://github.com/AutoMapper/AutoMapper">AutoMapper GitHub Repository</a></li>
</ul>

<h2 id="záver">Záver</h2>

<p>Na záver by som chcel zdôrazniť, že cesta vývojára je vždy individuálna a plná výziev. Každý z nás má iné tempo učenia sa, rôzne záujmy a preferencie. Tento článok ti mal poskytnúť návod, ktorým smerom sa môžeš vydať, no to, ako sa rozhodneš napredovať, je úplne na tebe. Nezabúdaj, že programovanie je o neustálom zlepšovaní a prispôsobovaní sa novým technológiám. Neboj sa urobiť chybu, uč sa z nej a hľadaj spôsoby, ako sa posunúť ďalej. Dôležité je neostať stáť na mieste a stále sa snažiť zlepšovať. Takže držím palce na tvojej ceste za lepším kódom a hlavne – užívaj si to!</p>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="ASP.NET CORE" /><category term="long read" /><summary type="html"><![CDATA[Úvod]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/junior-developer/1.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/junior-developer/1.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Creating UML / Graphs with Mermaid</title><link href="https://blog.burgyn.online/2024/08/02/creating-uml-graphs-with-mermaid" rel="alternate" type="text/html" title="Creating UML / Graphs with Mermaid" /><published>2024-08-02T17:00:00+00:00</published><updated>2024-08-02T17:00:00+00:00</updated><id>https://blog.burgyn.online/2024/08/02/creating-uml-graphs-with-mermaid</id><content type="html" xml:base="https://blog.burgyn.online/2024/08/02/creating-uml-graphs-with-mermaid"><![CDATA[<p>How do you draw UML diagrams or charts? Do you draw? Not really, but sometimes it comes in handy and it’s nice to have the some part of the system described with a sequence diagram, for example. 
In the past I used <a href="https://draw.io">draw.io</a>. Cool tool, but there was a standard problem with it. How to store the diagrams? How to add them to the documentation? How to share them? And the biggest problem how to edit them afterwards 🤔? 
I used <a href="https://plantuml.com/">PlantUML</a> a couple of times, which solved exactly this. I simply create the diagram using text directly in markdown, and the tool renders the outline for me at that point. For me it’s a great solution.</p>

<p>However, I discovered <a href="https://mermaid.js.org/">Mermaid</a>. Same idea, but given where and how it’s integrated everywhere, it’s a significantly better choice for me.</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sequenceDiagram
    participant Author
    participant Notion
    participant Blog1 as blog.burgyn.online
    participant Blog2 as blog.vyvojari.dev
    participant Social as Social media

    Author-&gt;&gt;Notion: Record ideas for articles
    Author-&gt;&gt;Notion: Select an idea to write about
    Author-&gt;&gt;Author: Write the basic structure of the article
    Note right of Author: Drink lots of coffee!
    Author-&gt;&gt;Author: Insert images and diagrams
    Author-&gt;&gt;Author: Check grammar and style
    Author-&gt;&gt;Blog1: Publish the article
    Author-&gt;&gt;Blog2: Publish the article
    Author-&gt;&gt;Author: Create graphic material
    Author-&gt;&gt;Social: Share the article from blog.burgyn.online
    Note right of Author: Wait for feedback!
</code></pre></div></div>

<script src=""></script>
<div class="mermaid">
sequenceDiagram
    participant Author
    participant Notion
    participant Blog1 as blog.burgyn.online
    participant Blog2 as blog.vyvojari.dev
    participant Social as Social media

    Author-&gt;&gt;Notion: Record ideas for articles
    Author-&gt;&gt;Notion: Select an idea to write about
    Author-&gt;&gt;Author: Write the basic structure of the article
    Note right of Author: Drink lots of coffee!
    Author-&gt;&gt;Author: Insert images and diagrams
    Author-&gt;&gt;Author: Check grammar and style
    Author-&gt;&gt;Blog1: Publish the article
    Author-&gt;&gt;Blog2: Publish the article
    Author-&gt;&gt;Author: Create graphic material
    Author-&gt;&gt;Social: Share the article from blog.burgyn.online
    Note right of Author: Wait for feedback!
</div>

<p>Benefits for me:</p>
<ul>
  <li>easy to use directly in markdown
    <ul>
      <li>so it’s part of the documentation</li>
      <li>native git support (versioning, sharing, …)</li>
      <li>post-editing</li>
    </ul>
  </li>
  <li>possibility to create different types of diagrams (flowchart, sequence, class, state, …)</li>
  <li>various integrations
    <ul>
      <li>Notion</li>
      <li>GitHub</li>
      <li>Jekyll (I used <a href="https://github.com/jasonbellamy/jekyll-mermaid">jekyll-mermaid</a>)</li>
      <li>VsCode</li>
      <li>AZURE DevOps (Wiki only for now)</li>
      <li>ChatGPT 🤣</li>
      <li>…</li>
    </ul>
  </li>
</ul>

<p><a href="https://docs.mermaidchart.com/plugins/mermaid-chart-gpt">Plugin for ChatGPT</a>. I created the diagram above with a simple prompt to ChatGPT:</p>

<blockquote>
  <p>I’m going to post an article just about Mermaid. Create me some interesting sequence diagram on how to write such a blog post. I’m writing the ideas in Notion, posting the post on blog.burgyn.online and blog.vyvojari.dev. I then do a social media post about it.</p>
</blockquote>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="tools" /><category term="architecture" /><summary type="html"><![CDATA[The blog outlines how to leverage tools like PlantUML, Mermaid for diagram creation in markdown, storage, and sharing.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/code_images/creating-uml-graphs-with-mermaid/cover.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/code_images/creating-uml-graphs-with-mermaid/cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Application Insights - Log Level</title><link href="https://blog.burgyn.online/2024/07/01/application-insights-log-level" rel="alternate" type="text/html" title="Application Insights - Log Level" /><published>2024-07-01T17:00:00+00:00</published><updated>2024-07-01T17:00:00+00:00</updated><id>https://blog.burgyn.online/2024/07/01/application-insights-log-level</id><content type="html" xml:base="https://blog.burgyn.online/2024/07/01/application-insights-log-level"><![CDATA[<p>🤦 I’ve been using ApplicationInsights for a few years now, but only now have I had a real need to look for my own custom logs there. It took me a while to figure out why I can’t find them there.</p>

<p>🤔 It’s not enough to set the generic “LogLeve:Default” to “Information” (Debug, Trace), because by default the ApplicationInsights provider only pushes logs with severity Warning and higher to AppInsights anyway.</p>

<p>💡 In order to push them there you need to explicitly set “ApplicationInsights:LogLevel:Default:” to “Information” (Debug, Trace).</p>

<p>🤞 Maybe this will help you sometime too.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Logging"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"LogLevel"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Default"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Information"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="err">//</span><span class="w"> </span><span class="err">👇</span><span class="w"> </span><span class="err">This</span><span class="w"> </span><span class="err">section</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">important</span><span class="w">
    </span><span class="nl">"ApplicationInsights"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"LogLevel"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"Default"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Information"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"ApplicationInsights"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"ConnectionString"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="AZURE" /><category term="csharp" /><category term="asp.net core" /><category term="logs" /><summary type="html"><![CDATA[Discover how to locate custom logs in Azure's ApplicationInsights and adjust settings so they're visible by default.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/blog-post-base-cover.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/blog-post-base-cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Arch tests - Check project references</title><link href="https://blog.burgyn.online/2024/06/23/arch-tests-check-project-references" rel="alternate" type="text/html" title="Arch tests - Check project references" /><published>2024-06-23T17:00:00+00:00</published><updated>2024-06-23T17:00:00+00:00</updated><id>https://blog.burgyn.online/2024/06/23/arch-tests-check-project-references</id><content type="html" xml:base="https://blog.burgyn.online/2024/06/23/arch-tests-check-project-references"><![CDATA[<p>In our company we have a large solution which contains more than 100 projects (not including test projects). Within that solution are WebAPI projects, libraries and AZURE functions. 
A couple of times it happened to us that our WebAPI projects referenced each other. Which is fundamentally wrong. They should be independent of each other and only reference other libraries.
<em>(we missed it during code review)</em></p>

<blockquote>
  <p>It is bad not only in principle, but also because MSBuild still has a problem and if you reference WebAPI projects in this way, it can non-deterministically give you <code class="highlighter-rouge">json</code> files in the output of one of the projects that are from another project. We have had this happen to us quite often.</p>
</blockquote>

<p>We decided to do a test on it. I don’t know if this is really the type of an architectural test, but let’s say it is 😊.</p>

<p>I originally tried to use the <code class="highlighter-rouge">Microsoft.Build</code> and <code class="highlighter-rouge">Microsoft.Build.Locator</code> libraries. These libraries contain the <code class="highlighter-rouge">Project</code> and <code class="highlighter-rouge">ProjectCollection</code> classes, which can retrieve project properties and references. <em>(Microsoft uses this for MSBuild)</em> But the problem with this was that it was very slow and had its flies.</p>

<p>Fortunately we didn’t need anything complicated from <code class="highlighter-rouge">csproj</code>, to be able to find out if it is a host project and its references.
So we read the necessary information from the <code class="highlighter-rouge">csproj</code> files directly via <code class="highlighter-rouge">XDocument</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">class</span> <span class="nc">ProjectFile</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">HashSet</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">_projectReferences</span> <span class="p">=</span> <span class="p">[];</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="n">DirectoryPath</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="n">FullPath</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="n">Sdk</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="n">OutputType</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">string</span> <span class="n">AzureFunctionsVersion</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">bool</span> <span class="n">IsWebProject</span>
        <span class="p">=&gt;</span> <span class="n">OutputType</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="s">"Exe"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span>
        <span class="p">||</span> <span class="n">Sdk</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="s">"Microsoft.NET.Sdk.Web"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span>
        <span class="p">||</span> <span class="n">AzureFunctionsVersion</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"v"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">);</span>

    <span class="k">public</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">ProjectsReferences</span> <span class="p">=&gt;</span> <span class="n">_projectReferences</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">ProjectFile</span><span class="p">&gt;</span> <span class="nf">LoadAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">projectFilePath</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">projectFile</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ProjectFile</span><span class="p">();</span>

        <span class="n">projectFile</span><span class="p">.</span><span class="n">Name</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">GetFileNameWithoutExtension</span><span class="p">(</span><span class="n">projectFilePath</span><span class="p">);</span>
        <span class="n">projectFile</span><span class="p">.</span><span class="n">FullPath</span> <span class="p">=</span> <span class="n">projectFilePath</span><span class="p">;</span>
        <span class="n">projectFile</span><span class="p">.</span><span class="n">DirectoryPath</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">GetDirectoryName</span><span class="p">(</span><span class="n">projectFilePath</span><span class="p">)</span> <span class="p">??</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

        <span class="k">using</span> <span class="nn">var</span> <span class="n">fileStream</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">FileStream</span><span class="p">(</span><span class="n">projectFilePath</span><span class="p">,</span> <span class="n">FileMode</span><span class="p">.</span><span class="n">Open</span><span class="p">,</span> <span class="n">FileAccess</span><span class="p">.</span><span class="n">Read</span><span class="p">);</span>

        <span class="kt">var</span> <span class="n">doc</span> <span class="p">=</span> <span class="k">await</span> <span class="n">XDocument</span><span class="p">.</span><span class="nf">LoadAsync</span><span class="p">(</span><span class="n">fileStream</span><span class="p">,</span> <span class="n">LoadOptions</span><span class="p">.</span><span class="n">None</span><span class="p">,</span> <span class="k">default</span><span class="p">);</span>

        <span class="kt">var</span> <span class="n">projectElement</span> <span class="p">=</span> <span class="n">doc</span><span class="p">.</span><span class="nf">Element</span><span class="p">(</span><span class="s">"Project"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">projectElement</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">projectFile</span><span class="p">.</span><span class="n">Sdk</span> <span class="p">=</span> <span class="n">projectElement</span><span class="p">.</span><span class="nf">Attribute</span><span class="p">(</span><span class="s">"Sdk"</span><span class="p">)?.</span><span class="n">Value</span> <span class="p">??</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

            <span class="kt">var</span> <span class="n">propertyGroup</span> <span class="p">=</span> <span class="n">projectElement</span><span class="p">.</span><span class="nf">Element</span><span class="p">(</span><span class="s">"PropertyGroup"</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">propertyGroup</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">projectFile</span><span class="p">.</span><span class="n">OutputType</span> <span class="p">=</span> <span class="n">propertyGroup</span><span class="p">.</span><span class="nf">Element</span><span class="p">(</span><span class="s">"OutputType"</span><span class="p">)?.</span><span class="n">Value</span> <span class="p">??</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
                <span class="n">projectFile</span><span class="p">.</span><span class="n">AzureFunctionsVersion</span> <span class="p">=</span> <span class="n">propertyGroup</span><span class="p">.</span><span class="nf">Element</span><span class="p">(</span><span class="s">"AzureFunctionsVersion"</span><span class="p">)?.</span><span class="n">Value</span> <span class="p">??</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">projectFile</span><span class="p">.</span><span class="n">_projectReferences</span> <span class="p">=</span> <span class="n">projectElement</span>
                <span class="p">.</span><span class="nf">Elements</span><span class="p">(</span><span class="s">"ItemGroup"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Elements</span><span class="p">(</span><span class="s">"ProjectReference"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Attributes</span><span class="p">(</span><span class="s">"Include"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">attr</span> <span class="p">=&gt;</span> <span class="n">attr</span><span class="p">.</span><span class="n">Value</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">ToHashSet</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">projectFile</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>be careful when using <code class="highlighter-rouge">Directory.Build.props</code>, then not all properties are directly in the <code class="highlighter-rouge">csproj</code> file.
We didn’t mind, though, because the necessary ones were there.</p>
</blockquote>

<p>We used the SDK property for ASP.NET Core WebAPI projects to determine if it is a host project and the <code class="highlighter-rouge">AzureFunctionsVersion</code> property for Azure Functions.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">bool</span> <span class="n">IsWebProject</span>
    <span class="p">=&gt;</span> <span class="n">OutputType</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="s">"Exe"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span>
    <span class="p">||</span> <span class="n">Sdk</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="s">"Microsoft.NET.Sdk.Web"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span>
    <span class="p">||</span> <span class="n">AzureFunctionsVersion</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"v"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">);</span>
</code></pre></div></div>

<p>A <code class="highlighter-rouge">ProjectFileCollection</code> class that retrieves all the projects in the subdirectories and their references.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">class</span> <span class="nc">ProjectFileCollection</span> <span class="p">:</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">ProjectFile</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">ProjectFile</span><span class="p">&gt;</span> <span class="n">_projects</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>

    <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">ProjectFileCollection</span><span class="p">&gt;</span> <span class="nf">LoadSolution</span><span class="p">(</span><span class="kt">string</span> <span class="n">solutionDirectory</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">projectFileCollection</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ProjectFileCollection</span><span class="p">();</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">projectFile</span> <span class="k">in</span> <span class="n">Directory</span><span class="p">.</span><span class="nf">GetFiles</span><span class="p">(</span><span class="n">solutionDirectory</span><span class="p">,</span> <span class="s">"*.csproj"</span><span class="p">,</span> <span class="n">SearchOption</span><span class="p">.</span><span class="n">AllDirectories</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">project</span> <span class="p">=</span> <span class="k">await</span> <span class="n">ProjectFile</span><span class="p">.</span><span class="nf">LoadAsync</span><span class="p">(</span><span class="n">projectFile</span><span class="p">);</span>
            <span class="n">projectFileCollection</span><span class="p">.</span><span class="n">_projects</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">project</span><span class="p">.</span><span class="n">FullPath</span><span class="p">,</span> <span class="n">project</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">projectFileCollection</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">ProjectFile</span> <span class="nf">GetProject</span><span class="p">(</span><span class="kt">string</span> <span class="n">projectPath</span><span class="p">)</span>
        <span class="p">=&gt;</span> <span class="n">_projects</span><span class="p">[</span><span class="n">projectPath</span><span class="p">];</span>

    <span class="k">public</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">ProjectFile</span><span class="p">&gt;</span> <span class="nf">GetProjectReferences</span><span class="p">(</span><span class="n">ProjectFile</span> <span class="n">project</span><span class="p">)</span>
        <span class="p">=&gt;</span> <span class="n">project</span><span class="p">.</span><span class="n">ProjectsReferences</span>
            <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="nf">GetProject</span><span class="p">(</span><span class="nf">GetReferenceFullPath</span><span class="p">(</span><span class="n">project</span><span class="p">.</span><span class="n">DirectoryPath</span><span class="p">,</span> <span class="n">p</span><span class="p">)));</span>

    <span class="k">private</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">GetReferenceFullPath</span><span class="p">(</span><span class="kt">string</span> <span class="n">projectDir</span><span class="p">,</span> <span class="kt">string</span> <span class="n">referencePath</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="nf">IsPathRooted</span><span class="p">(</span><span class="n">referencePath</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="n">referencePath</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">Path</span><span class="p">.</span><span class="nf">GetFullPath</span><span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">projectDir</span><span class="p">,</span> <span class="n">referencePath</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">IEnumerator</span><span class="p">&lt;</span><span class="n">ProjectFile</span><span class="p">&gt;</span> <span class="nf">GetEnumerator</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_projects</span><span class="p">.</span><span class="n">Values</span><span class="p">.</span><span class="nf">GetEnumerator</span><span class="p">();</span>

    <span class="n">IEnumerator</span> <span class="n">IEnumerable</span><span class="p">.</span><span class="nf">GetEnumerator</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="nf">GetEnumerator</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The test itself can then look like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Fact</span><span class="p">]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">WebProjects_ShouldNotReferenceOtherWebProjects</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">string</span> <span class="n">solutionDirectory</span> <span class="p">=</span> <span class="nf">FindSolutionDirectory</span><span class="p">();</span>
    <span class="kt">var</span> <span class="n">projects</span> <span class="p">=</span> <span class="k">await</span> <span class="n">ProjectFileCollection</span><span class="p">.</span><span class="nf">LoadSolution</span><span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">solutionDirectory</span><span class="p">,</span> <span class="s">"src"</span><span class="p">));</span>

    <span class="kt">var</span> <span class="n">webProjects</span> <span class="p">=</span> <span class="n">projects</span><span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">IsWebProject</span><span class="p">);</span>
    <span class="kt">var</span> <span class="n">errorMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StringBuilder</span><span class="p">();</span>
    <span class="kt">var</span> <span class="n">wrongRecerencesCount</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>

    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">webProject</span> <span class="k">in</span> <span class="n">webProjects</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">projectReferences</span> <span class="p">=</span> <span class="n">projects</span><span class="p">.</span><span class="nf">GetProjectReferences</span><span class="p">(</span><span class="n">webProject</span><span class="p">);</span>
        <span class="kt">var</span> <span class="n">webProjectReferences</span> <span class="p">=</span> <span class="n">projectReferences</span><span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">IsWebProject</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">webProjectReferences</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
        <span class="p">{</span>
            <span class="n">errorMessage</span><span class="p">.</span><span class="nf">AppendFormat</span><span class="p">(</span><span class="s">"&gt; {0}:"</span><span class="p">,</span> <span class="n">webProject</span><span class="p">.</span><span class="n">Name</span><span class="p">).</span><span class="nf">AppendLine</span><span class="p">();</span>
            <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">reference</span> <span class="k">in</span> <span class="n">webProjectReferences</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">errorMessage</span><span class="p">.</span><span class="nf">AppendFormat</span><span class="p">(</span><span class="s">"\t- {0}"</span><span class="p">,</span> <span class="n">reference</span><span class="p">.</span><span class="n">Name</span><span class="p">).</span><span class="nf">AppendLine</span><span class="p">();</span>
            <span class="p">}</span>
            <span class="n">wrongRecerencesCount</span><span class="p">++;</span>
            <span class="n">errorMessage</span><span class="p">.</span><span class="nf">AppendLine</span><span class="p">(</span><span class="s">"----------------------------------------"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="n">wrongRecerencesCount</span><span class="p">.</span><span class="nf">Should</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">Be</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="s">"Web project should not reference other web project:\n"</span> <span class="p">+</span> <span class="n">errorMessage</span><span class="p">.</span><span class="nf">ToString</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The result may look like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Expected wrongRecerencesCount to be 0 because Web project should not reference other web project:
&gt; Kros.Esw.ApiProjectA:
	- Kros.Esw.ApiProjectB
	- Kros.Esw.ApiProjectC
	- Kros.Esw.ApiProjectD
----------------------------------------
, but found 1 (difference of 1).
</code></pre></div></div>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="csharp" /><category term="architecture" /><category term="unit tests" /><category term="AZURE" /><summary type="html"><![CDATA[The blog post discusses managing project references in WebAPI projects featuring C# codes to perform architectural tests and maintain project independence.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/code_images/arch-tests-check-project-references/cover.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/code_images/arch-tests-check-project-references/cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Own mediator</title><link href="https://blog.burgyn.online/2024/06/09/own-mediator" rel="alternate" type="text/html" title="Own mediator" /><published>2024-06-09T17:00:00+00:00</published><updated>2024-06-09T17:00:00+00:00</updated><id>https://blog.burgyn.online/2024/06/09/own-mediator</id><content type="html" xml:base="https://blog.burgyn.online/2024/06/09/own-mediator"><![CDATA[<p>Currently, the Mediator design pattern is often used. Its purpose is to separate the communication between objects from each other. Which will reduce dependencies between classes, make the code more transparent and simplify testing. 
In a .NET environment, a developer often reaches for the cool library <a href="https://github.com/jbogard/MediatR">MediatR</a> by Jimmy Bogard. This library provides an easy way to implement the Mediator pattern into an application.</p>

<p>However, there are situations where we can’t/don’t want to use a third party library. For example, you are writing some custom library/framework and you don’t want to introduce such dependencies into it.</p>

<p>Something similar happened to us. Fortunately, we didn’t need the complete functionality that MediatR provides, but just a basic event dispatch and processing was enough.</p>

<p>Therefore, I will now show how to easily create such an implementation of your own Mediator.</p>

<p>Let’s start with defining the message interface. In our case it was a domain event, hence the name.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IDomainEvent</span>
<span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>It’s just a pure markup interface. We could do without it, but it’s a good way to indicate that it’s an event.</p>
</blockquote>

<p>Then the event processing interface.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;</span> 
    <span class="k">where</span> <span class="n">TEvent</span> <span class="p">:</span> <span class="n">IDomainEvent</span>
<span class="p">{</span>
    <span class="n">Task</span> <span class="nf">HandleAsync</span><span class="p">(</span><span class="n">TEvent</span> <span class="n">domainEvent</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">default</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We need an interface for sending events.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IEventPublisher</span>
<span class="p">{</span>
    <span class="n">Task</span> <span class="n">PublishAsync</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;(</span><span class="n">TEvent</span> <span class="n">domainEvent</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">default</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TEvent</span> <span class="p">:</span> <span class="n">IDomainEvent</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We could stop with the abstractions here and start implementing. However, we decided to add an abstraction for the processing strategy in there. For now, the sequential processing of events was enough, but we might need other strategies in the future.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IEventPublisherStrategy</span>
<span class="p">{</span>
    <span class="n">Task</span> <span class="n">PublishAsync</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;(</span>
        <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;&gt;</span> <span class="n">handlers</span><span class="p">,</span>
        <span class="n">TEvent</span> <span class="n">domainEvent</span><span class="p">,</span>
        <span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TEvent</span> <span class="p">:</span> <span class="n">IDomainEvent</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="implementation">Implementation</h2>

<p>Publisher can look like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">class</span> <span class="nc">EventPublisher</span> <span class="p">:</span> <span class="n">IEventPublisher</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IServiceProvider</span> <span class="n">_serviceProvider</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IEventPublisherStrategy</span> <span class="n">_publisherStrategy</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IHttpContextAccessor</span> <span class="n">_httpContextAccessor</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">EventPublisher</span><span class="p">(</span>
        <span class="n">IServiceProvider</span> <span class="n">serviceProvider</span><span class="p">,</span>
        <span class="n">IHttpContextAccessor</span> <span class="n">httpContextAccessor</span><span class="p">,</span>
        <span class="n">IEventPublisherStrategy</span> <span class="n">publisherStrategy</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_serviceProvider</span> <span class="p">=</span> <span class="n">serviceProvider</span><span class="p">;</span>
        <span class="n">_publisherStrategy</span> <span class="p">=</span> <span class="n">publisherStrategy</span><span class="p">;</span>
        <span class="n">_httpContextAccessor</span> <span class="p">=</span> <span class="n">httpContextAccessor</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="n">PublishAsync</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;(</span><span class="n">TEvent</span> <span class="n">domainEvent</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">default</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TEvent</span> <span class="p">:</span> <span class="n">IDomainEvent</span>
    <span class="p">{</span>
        <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;&gt;</span> <span class="n">handlers</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_httpContextAccessor</span><span class="p">.</span><span class="n">HttpContext</span> <span class="k">is</span> <span class="n">not</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="c1">// 👇 if available, then find handlers in HttpContext services</span>
            <span class="n">handlers</span> <span class="p">=</span> <span class="n">GetHandlers</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;(</span><span class="n">_httpContextAccessor</span><span class="p">.</span><span class="n">HttpContext</span><span class="p">.</span><span class="n">RequestServices</span><span class="p">);</span>
            <span class="c1">// 👇 publish event to all handlers</span>
            <span class="k">await</span> <span class="n">_publisherStrategy</span><span class="p">.</span><span class="nf">PublishAsync</span><span class="p">(</span><span class="n">handlers</span><span class="p">,</span> <span class="n">domainEvent</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">else</span>
        <span class="p">{</span>
            <span class="c1">// 👇 if not, then create new scope</span>
            <span class="k">using</span> <span class="nn">var</span> <span class="n">scope</span> <span class="p">=</span> <span class="n">_serviceProvider</span><span class="p">.</span><span class="nf">CreateScope</span><span class="p">();</span>
            <span class="n">handlers</span> <span class="p">=</span> <span class="n">GetHandlers</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;(</span><span class="n">scope</span><span class="p">.</span><span class="n">ServiceProvider</span><span class="p">);</span>
            <span class="c1">// 👇 publish event to all handlers</span>
            <span class="k">await</span> <span class="n">_publisherStrategy</span><span class="p">.</span><span class="nf">PublishAsync</span><span class="p">(</span><span class="n">handlers</span><span class="p">,</span> <span class="n">domainEvent</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">static</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;&gt;</span> <span class="n">GetHandlers</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;(</span><span class="n">IServiceProvider</span> <span class="n">serviceProvider</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TEvent</span> <span class="p">:</span> <span class="n">IDomainEvent</span>
        <span class="p">=&gt;</span> <span class="n">serviceProvider</span><span class="p">.</span><span class="n">GetServices</span><span class="p">&lt;</span><span class="n">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;&gt;();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Publisher takes care of getting the handlers from the container and then forwards them to publisher strategy, which decides how they will be processed.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">ForeachAwaitPublisherStrategy</span> <span class="p">:</span> <span class="n">IEventPublisherStrategy</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="n">PublishAsync</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;(</span>
        <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;&gt;</span> <span class="n">handlers</span><span class="p">,</span>
        <span class="n">TEvent</span> <span class="n">domainEvent</span><span class="p">,</span>
        <span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TEvent</span> <span class="p">:</span> <span class="n">IDomainEvent</span>
    <span class="p">{</span>
        <span class="c1">// 👇 publish event to all handlers</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">handler</span> <span class="k">in</span> <span class="n">handlers</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">await</span> <span class="n">handler</span><span class="p">.</span><span class="nf">HandleAsync</span><span class="p">(</span><span class="n">domainEvent</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>Our <code class="highlighter-rouge">ForeachAwaitPublisherStrategy</code> handles events sequentially. It forwards the message to all handlers and waits for them to process it.</p>

<h2 id="registration">Registration</h2>

<p>In order to use the abstractions we create, we need to register them in the DI container.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="n">IServiceCollection</span> <span class="nf">AddEventPublisher</span><span class="p">(</span><span class="k">this</span> <span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddHttpContextAccessor</span><span class="p">();</span>
    <span class="n">services</span><span class="p">.</span><span class="n">TryAddSingleton</span><span class="p">&lt;</span><span class="n">IEventPublisher</span><span class="p">,</span> <span class="n">EventPublisher</span><span class="p">&gt;();</span>
    <span class="n">services</span><span class="p">.</span><span class="n">TryAddSingleton</span><span class="p">&lt;</span><span class="n">IEventPublisherStrategy</span><span class="p">,</span> <span class="n">ForeachAwaitPublisherStrategy</span><span class="p">&gt;();</span>
    <span class="k">return</span> <span class="n">services</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We can also create an extension for easy registration of handlers.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="n">IServiceCollection</span> <span class="n">AddDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">,</span> <span class="n">THandler</span><span class="p">&gt;(</span>
    <span class="k">this</span> <span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">,</span>
    <span class="n">ServiceLifetime</span> <span class="n">lifetime</span> <span class="p">=</span> <span class="n">ServiceLifetime</span><span class="p">.</span><span class="n">Transient</span><span class="p">)</span>
    <span class="k">where</span> <span class="n">TEvent</span> <span class="p">:</span> <span class="n">IDomainEvent</span>
    <span class="k">where</span> <span class="n">THandler</span> <span class="p">:</span> <span class="k">class</span><span class="err">,</span> <span class="nc">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">ServiceDescriptor</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">TEvent</span><span class="p">&gt;),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">THandler</span><span class="p">),</span> <span class="n">lifetime</span><span class="p">));</span>

    <span class="k">return</span> <span class="n">services</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>Using this extension looks like this <code class="highlighter-rouge">services.AddDomainEventHandler&lt;YourEvent, YourHandler&gt;();</code>. 
This is because I wanted to avoid reflection. If reflection doesn’t bother you, a simplified call to <code class="highlighter-rouge">services.AddDomainEventHandler&lt;YourHandler&gt;();</code>. 
But I’ll leave that up to you 😉.</p>
</blockquote>

<h2 id="usage">Usage</h2>

<p>Finally, we’ll show you how to use our own Mediator.</p>

<p>Event and handler:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 👇 The event that the product was created</span>
<span class="k">public</span> <span class="n">record</span> <span class="nf">ProductCreated</span><span class="p">(</span><span class="kt">int</span> <span class="n">Id</span><span class="p">,</span> <span class="kt">string</span> <span class="n">Name</span><span class="p">,</span> <span class="kt">decimal</span> <span class="n">Price</span><span class="p">)</span> <span class="p">:</span> <span class="n">IDomainEvent</span><span class="p">;</span>

<span class="c1">// 👇 The handler that will process the event</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ProductCreatedHandler</span> <span class="p">:</span> <span class="n">IDomainEventHandler</span><span class="p">&lt;</span><span class="n">ProductCreated</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Task</span> <span class="nf">HandleAsync</span><span class="p">(</span><span class="n">ProductCreated</span> <span class="n">domainEvent</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">default</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 👇 Process the event</span>
        <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Product created: </span><span class="p">{</span><span class="n">domainEvent</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The registration:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddEventPublisher</span><span class="p">();</span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddDomainEventHandler</span><span class="p">&lt;</span><span class="n">ProductCreated</span><span class="p">,</span> <span class="n">ProductCreatedHandler</span><span class="p">&gt;();</span>
</code></pre></div></div>

<p>Use in endpoint:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">MapPost</span><span class="p">(</span><span class="s">"/products"</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="n">Product</span> <span class="n">product</span><span class="p">,</span> <span class="n">IEventPublisher</span> <span class="n">publisher</span><span class="p">)</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="c1">// Save the product</span>
    <span class="c1">// 👇 Publish the event</span>
    <span class="k">await</span> <span class="n">publisher</span><span class="p">.</span><span class="nf">PublishAsync</span><span class="p">(</span><span class="k">new</span> <span class="nf">ProductCreated</span><span class="p">(</span><span class="n">product</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">product</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">product</span><span class="p">.</span><span class="n">Price</span><span class="p">));</span>
    <span class="k">return</span> <span class="n">product</span><span class="p">;</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="links">Links</h2>

<ul>
  <li><a href="https://github.com/Burgyn/MMLib.Sample.Mediator">Sample project</a></li>
</ul>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="csharp" /><category term="dotnet" /><category term="unit tests" /><category term="architecture" /><summary type="html"><![CDATA[Discover how to build your own implementation of the Mediator design pattern in a .NET environment without third-party libraries.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/code_images/own-mediator/cover.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/code_images/own-mediator/cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Ocelot ETag Caching</title><link href="https://blog.burgyn.online/2024/06/02/ocelot-etag-caching" rel="alternate" type="text/html" title="Ocelot ETag Caching" /><published>2024-06-02T17:00:00+00:00</published><updated>2024-06-02T17:00:00+00:00</updated><id>https://blog.burgyn.online/2024/06/02/ocelot-etag-caching</id><content type="html" xml:base="https://blog.burgyn.online/2024/06/02/ocelot-etag-caching"><![CDATA[<p>The <code class="highlighter-rouge">Kros.Ocelot.ETagCaching</code> library brings ETag caching support to the Ocelot API Gateway. ETag caching is an HTTP caching mechanism that allows clients to verify if the cached data is still current without downloading the entire resource again. If the data hasn’t changed, the server responds with a <code class="highlighter-rouge">304 Not Modified</code> status, prompting the client to use the cached data, thereby saving bandwidth and reducing server load.</p>

<h2 id="why-use-etag-caching">Why Use ETag Caching?</h2>

<p>ETag caching is particularly useful in scenarios where data changes infrequently but is requested frequently. It optimizes network usage and improves response times by minimizing data transfer. By using ETags, clients can ensure they always have the most recent version of the data.</p>

<h2 id="how-etag-caching-works">How ETag Caching Works</h2>

<p>ETag caching involves the use of two HTTP headers:</p>

<ul>
  <li><code class="highlighter-rouge">ETag</code>: A unique identifier for the data, typically a hash or a version number.</li>
  <li><code class="highlighter-rouge">cache-control</code>: Instructs the client that the response can be cached. For ETag caching, this should be set to <code class="highlighter-rouge">private</code>.</li>
</ul>

<p>When a client receives a response with these headers, it caches the data and the ETag value. On subsequent requests, the client sends an <code class="highlighter-rouge">If-None-Match</code> header with the ETag value. The server then compares the ETag with the current version of the data. If they match, the server returns a <code class="highlighter-rouge">304 Not Modified</code> status, indicating the client should use the cached data.</p>

<h2 id="implementation-in-ocelot">Implementation in Ocelot</h2>

<p>The <code class="highlighter-rouge">Kros.Ocelot.ETagCaching</code> library integrates seamlessly with Ocelot’s middleware. It handles ETag generation, storage, and validation transparently. Here’s how to get started:</p>

<h3 id="ocelot-configuration">Ocelot Configuration</h3>

<p>Configure your routes in the <code class="highlighter-rouge">ocelot.json</code> file, specifying cache policies:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"Routes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"getAllProducts"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"DownstreamPathTemplate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/api/products/"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"UpstreamPathTemplate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/products/"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"CachePolicy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"getAllProducts"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">👈</span><span class="w"> </span><span class="err">Cache</span><span class="w"> </span><span class="err">policy</span><span class="w"> </span><span class="err">key</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"getProduct"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"DownstreamPathTemplate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/api/products/{id}"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"UpstreamPathTemplate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/products/{id}"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"CachePolicy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"getProduct"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">👈</span><span class="w"> </span><span class="err">Cache</span><span class="w"> </span><span class="err">policy</span><span class="w"> </span><span class="err">key</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deleteProduct"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"DownstreamPathTemplate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/api/products/{id}"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"UpstreamPathTemplate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/products/{id}"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"InvalidateCachePolicy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"invalidateProductCachePolicy"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="programcs-configuration">Program.cs Configuration</h3>

<p>Set up the ETag caching policies in <code class="highlighter-rouge">Program.cs</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 👇 Define policies</span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddOcelotETagCaching</span><span class="p">((</span><span class="n">c</span><span class="p">)</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="c1">//  👇 Simple policy with expiration and tag templates</span>
    <span class="n">c</span><span class="p">.</span><span class="nf">AddPolicy</span><span class="p">(</span><span class="s">"getAllProducts"</span><span class="p">,</span> <span class="n">p</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="n">p</span><span class="p">.</span><span class="nf">Expire</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromMinutes</span><span class="p">(</span><span class="m">5</span><span class="p">));</span>
        <span class="n">p</span><span class="p">.</span><span class="nf">TagTemplates</span><span class="p">(</span><span class="s">"products:{tenantId}"</span><span class="p">,</span> <span class="s">"all"</span><span class="p">,</span> <span class="s">"tenantAll:{tenantId}"</span><span class="p">);</span>
    <span class="p">});</span>

    <span class="c1">// 👇 Policy with custom cache key, ETag generator, and cache control</span>
    <span class="n">c</span><span class="p">.</span><span class="nf">AddPolicy</span><span class="p">(</span><span class="s">"getProduct"</span><span class="p">,</span> <span class="n">p</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="n">p</span><span class="p">.</span><span class="nf">Expire</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromMinutes</span><span class="p">(</span><span class="m">5</span><span class="p">));</span>
        <span class="n">p</span><span class="p">.</span><span class="nf">TagTemplates</span><span class="p">(</span><span class="s">"product:{tenantId}:{id}"</span><span class="p">,</span> <span class="s">"tenant:{tenantId}:all"</span><span class="p">,</span> <span class="s">"all"</span><span class="p">);</span>
        <span class="n">p</span><span class="p">.</span><span class="nf">CacheKey</span><span class="p">(</span><span class="n">context</span> <span class="p">=&gt;</span> <span class="n">context</span><span class="p">.</span><span class="n">Request</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="nf">GetValues</span><span class="p">(</span><span class="s">"id"</span><span class="p">).</span><span class="nf">FirstOrDefault</span><span class="p">());</span>
        <span class="n">p</span><span class="p">.</span><span class="nf">ETag</span><span class="p">(</span><span class="n">context</span> <span class="p">=&gt;</span> <span class="k">new</span><span class="p">(</span><span class="s">$""</span><span class="p">{</span><span class="n">Guid</span><span class="p">.</span><span class="nf">NewGuid</span><span class="p">()}</span><span class="s">""</span><span class="p">));</span>
        <span class="n">p</span><span class="p">.</span><span class="nf">CacheControl</span><span class="p">(</span><span class="k">new</span><span class="p">()</span> <span class="p">{</span> <span class="n">Public</span> <span class="p">=</span> <span class="k">false</span> <span class="p">});</span>
    <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Add the ETag caching middleware to the Ocelot pipeline:</p>

<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.UseOcelot(c =&gt;
{
    // 👇 Add ETag caching middleware
    c.AddETagCaching();
}).Wait();
</code></pre></div></div>

<h2 id="tag-templates-and-cache-invalidation">Tag Templates and Cache Invalidation</h2>

<p>Tag templates are used to generate cache tags based on request parameters, making it easy to invalidate specific cache entries. For example, for the route <code class="highlighter-rouge">/api/{tenantId}/products/{id}</code> and the tag template <code class="highlighter-rouge">product:{tenantId}:{id}</code>, the tag will be <code class="highlighter-rouge">product:1:2</code>.</p>

<h3 id="automatic-cache-invalidation">Automatic Cache Invalidation</h3>

<p>Define invalidate cache policies in <code class="highlighter-rouge">ocelot.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deleteProduct"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"UpstreamHttpMethod"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"Delete"</span><span class="w"> </span><span class="p">],</span><span class="w">
    </span><span class="nl">"DownstreamPathTemplate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/api/products/{id}"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"UpstreamPathTemplate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/products/{id}"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"InvalidateCachePolicy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"invalidateProductCachePolicy"</span><span class="w"> 
    </span><span class="err">//</span><span class="w"> </span><span class="err">👆</span><span class="w"> </span><span class="err">Invalidate</span><span class="w"> </span><span class="err">cache</span><span class="w"> </span><span class="err">policy</span><span class="w"> </span><span class="err">key</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>And configure them in <code class="highlighter-rouge">Program.cs</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddOcelotETagCaching</span><span class="p">(</span><span class="n">conf</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="c1">// 👇 Define invalidate cache policy</span>
    <span class="n">conf</span><span class="p">.</span><span class="nf">AddInvalidatePolicy</span><span class="p">(</span><span class="s">"invalidateProductCachePolicy"</span><span class="p">,</span> <span class="n">builder</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="c1">// 👇 Define tag templates to invalidate</span>
        <span class="n">builder</span><span class="p">.</span><span class="nf">TagTemplates</span><span class="p">(</span><span class="s">"product:{tenantId}"</span><span class="p">,</span> <span class="s">"product:{tenantId}:{id}"</span><span class="p">);</span>
    <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<h3 id="manual-cache-invalidation">Manual Cache Invalidation</h3>

<p>Manually invalidate cache entries using the <code class="highlighter-rouge">IOutputCacheStore</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ProductsService</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IOutputCacheStore</span> <span class="n">_outputCacheStore</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">ProductsService</span><span class="p">(</span><span class="n">IOutputCacheStore</span> <span class="n">outputCacheStore</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_outputCacheStore</span> <span class="p">=</span> <span class="n">outputCacheStore</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">DeleteProduct</span><span class="p">(</span><span class="kt">int</span> <span class="n">tenantId</span><span class="p">,</span> <span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">await</span> <span class="n">_outputCacheStore</span><span class="p">.</span><span class="nf">InvalidateAsync</span><span class="p">(</span>
            <span class="s">$"product:</span><span class="p">{</span><span class="n">tenantId</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="s">$"product:</span><span class="p">{</span><span class="n">tenantId</span><span class="p">}</span><span class="s">:</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
        <span class="c1">// 👆 Invalidate cache entries by tags            </span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="redis-integration">Redis Integration</h2>

<p>By default, the library uses <code class="highlighter-rouge">InMemoryCacheStore</code>, but you can configure it to use Redis for distributed caching:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddStackExchangeRedisOutputCache</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="n">options</span><span class="p">.</span><span class="n">Configuration</span> 
        <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetConnectionString</span><span class="p">(</span><span class="s">"MyRedisConStr"</span><span class="p">);</span>
    <span class="n">options</span><span class="p">.</span><span class="n">InstanceName</span> <span class="p">=</span> <span class="s">"SampleInstance"</span><span class="p">;</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="sources">Sources</h2>

<ul>
  <li><a href="https://github.com/Kros-sk/Kros.Ocelot.ETagCaching">Kros.Ocelot.ETagCaching GitHub Repository</a></li>
  <li><a href="https://ocelot.readthedocs.io/en/latest/">Ocelot</a></li>
</ul>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="csharp" /><category term="dotnet" /><category term="caching" /><category term="architecture" /><summary type="html"><![CDATA[A blog post discussing the use of ETag caching with Kros.Ocelot.ETagCaching library and Ocelot API Gateway.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/code_images/ocelot-etag-caching/cover.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/code_images/ocelot-etag-caching/cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">ASP.NET CORE Minimal API - Content Negotiation</title><link href="https://blog.burgyn.online/2024/05/19/asp-net-core-minimal-api-content-negotiation" rel="alternate" type="text/html" title="ASP.NET CORE Minimal API - Content Negotiation" /><published>2024-05-19T17:00:00+00:00</published><updated>2024-05-19T17:00:00+00:00</updated><id>https://blog.burgyn.online/2024/05/19/asp-net-core-minimal-api-content-negotiation</id><content type="html" xml:base="https://blog.burgyn.online/2024/05/19/asp-net-core-minimal-api-content-negotiation"><![CDATA[<p>The primary goal of <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/overview?view=aspnetcore-8.0">ASP.NET Core Minimal API</a> is to deliver a straightforward, simple, and most importantly, very powerful framework for creating APIs. Rather than delivering features to cover every possible usage scenario <em>(unlike <a href="https://learn.microsoft.com/en-us/aspnet/core/tutorials/choose-web-ui?view=aspnetcore-8.0">MVC</a>)</em> it is designed with certain specific patterns in mind. One of these cases is that the Minimal API consumes and produces only the <code class="highlighter-rouge">JSON</code> format <em>(Thus, it does not support <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation">Content Negotiation</a> which is handled by <a href="https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-8.0#content-negotiation">Output formatters</a> in the case of MVC)</em>.
However, there are situations where you really need either a different type of output <em>(for example, standard <code class="highlighter-rouge">XML</code>)</em>, or you need to be more in control of the serialization process, and you want to take advantage of the simplicity and power of the Minimal API. In this article, I will try to show how you can create your own support for Content Negotiation in the Minimal API.</p>

<blockquote>
  <p>⚠️ This is a functional implementation, but it doesn’t cover all scenarios and is just a basic example of what such support could look like.</p>
</blockquote>

<p>The whole solution is based on a custom implementation for <code class="highlighter-rouge">IResult</code>. <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/responses?view=aspnetcore-8.0">More info in the documentation</a>.</p>

<h2 id="iresponsenegotiator"><code class="highlighter-rouge">IResponseNegotiator</code></h2>

<p>Let’s first define the interface whose implementation will be responsible for serializing the response.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IResponseNegotiator</span>
<span class="p">{</span>
    <span class="kt">string</span> <span class="n">ContentType</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>

    <span class="kt">bool</span> <span class="nf">CanHandle</span><span class="p">(</span><span class="n">MediaTypeHeaderValue</span> <span class="n">accept</span><span class="p">);</span>

    <span class="n">Task</span> <span class="n">Handle</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;(</span><span class="n">HttpContext</span> <span class="n">httpContext</span><span class="p">,</span> <span class="n">TResult</span> <span class="n">result</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="implementation-for-json">Implementation for JSON</h2>

<p>For JSON, we can directly use <code class="highlighter-rouge">Ok&lt;TResult&gt;</code> result, which is part of the Minimal API.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">JsonNegotiator</span> <span class="p">:</span> <span class="n">IResponseNegotiator</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">ContentType</span> <span class="p">=&gt;</span> <span class="n">MediaTypeNames</span><span class="p">.</span><span class="n">Application</span><span class="p">.</span><span class="n">Json</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">CanHandle</span><span class="p">(</span><span class="n">MediaTypeHeaderValue</span> <span class="n">accept</span><span class="p">)</span>
        <span class="p">=&gt;</span> <span class="n">accept</span><span class="p">.</span><span class="n">MediaType</span> <span class="p">==</span> <span class="n">ContentType</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">Task</span> <span class="n">Handle</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;(</span><span class="n">HttpContext</span> <span class="n">httpContext</span><span class="p">,</span> <span class="n">TResult</span> <span class="n">result</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 👇 Use original Ok&lt;TResult&gt; type for JSON serialization</span>
        <span class="k">return</span> <span class="n">TypedResults</span><span class="p">.</span><span class="nf">Ok</span><span class="p">(</span><span class="n">result</span><span class="p">).</span><span class="nf">ExecuteAsync</span><span class="p">(</span><span class="n">httpContext</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="implementation-for-xml">Implementation for XML</h2>

<p>For XML, we can create a custom implementation that uses, for example, <code class="highlighter-rouge">DataContractSerializer</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">XmlNegotiator</span> <span class="p">:</span> <span class="n">IResponseNegotiator</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">ContentType</span> <span class="p">=&gt;</span> <span class="n">MediaTypeNames</span><span class="p">.</span><span class="n">Application</span><span class="p">.</span><span class="n">Xml</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">bool</span> <span class="nf">CanHandle</span><span class="p">(</span><span class="n">MediaTypeHeaderValue</span> <span class="n">accept</span><span class="p">)</span>
        <span class="p">=&gt;</span> <span class="n">accept</span><span class="p">.</span><span class="n">MediaType</span> <span class="p">==</span> <span class="n">ContentType</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="n">Handle</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;(</span><span class="n">HttpContext</span> <span class="n">httpContext</span><span class="p">,</span> <span class="n">TResult</span> <span class="n">result</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">httpContext</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">ContentType</span> <span class="p">=</span> <span class="n">ContentType</span><span class="p">;</span>

        <span class="c1">// 👇 Use DataContractSerializer for XML serialization</span>
        <span class="k">using</span> <span class="nn">var</span> <span class="n">stream</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">FileBufferingWriteStream</span><span class="p">();</span>
        <span class="k">using</span> <span class="nn">var</span> <span class="n">streamWriter</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StreamWriter</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
        <span class="kt">var</span> <span class="n">serializer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DataContractSerializer</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="nf">GetType</span><span class="p">());</span>

        <span class="n">serializer</span><span class="p">.</span><span class="nf">WriteObject</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>

        <span class="k">await</span> <span class="n">stream</span><span class="p">.</span><span class="nf">DrainBufferAsync</span><span class="p">(</span><span class="n">httpContext</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">Body</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="registration">Registration</h2>

<p>Unfortunately, due to the way <code class="highlighter-rouge">IEndpointMetadataProvider</code> works and the way serialization works directly in the Minimal API, I couldn’t find an elegant way to use the DI container <em>(there would be a few inelegant ones 😊).</em> So I resorted to a custom registrar for negotiators.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">ContentNegotiationProvider</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">IResponseNegotiator</span><span class="p">&gt;</span> <span class="n">_negotiators</span> <span class="p">=</span> <span class="p">[];</span>

    <span class="c1">// 👇 Internal list of negotiators</span>
    <span class="k">internal</span> <span class="k">static</span> <span class="n">IReadOnlyList</span><span class="p">&lt;</span><span class="n">IResponseNegotiator</span><span class="p">&gt;</span> <span class="n">Negotiators</span> <span class="p">=&gt;</span> <span class="n">_negotiators</span><span class="p">;</span>

    <span class="c1">// 👇 Add negotiator to the list</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="n">AddNegotiator</span><span class="p">&lt;</span><span class="n">TNegotiator</span><span class="p">&gt;()</span>
        <span class="k">where</span> <span class="n">TNegotiator</span> <span class="p">:</span> <span class="n">IResponseNegotiator</span><span class="p">,</span> <span class="k">new</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">_negotiators</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">TNegotiator</span><span class="p">());</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We register the negotiators in <code class="highlighter-rouge">Program.cs</code> or <code class="highlighter-rouge">Startup.cs</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ContentNegotiationProvider</span><span class="p">.</span><span class="n">AddNegotiator</span><span class="p">&lt;</span><span class="n">JsonNegotiator</span><span class="p">&gt;();</span>
<span class="n">ContentNegotiationProvider</span><span class="p">.</span><span class="n">AddNegotiator</span><span class="p">&lt;</span><span class="n">XmlNegotiator</span><span class="p">&gt;();</span>
</code></pre></div></div>

<h2 id="contentnegotiationresulttresult"><code class="highlighter-rouge">ContentNegotiationResult&lt;TResult&gt;</code></h2>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ContentNegotiationResult</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;(</span><span class="n">TResult</span> <span class="n">result</span><span class="p">)</span>
    <span class="p">:</span> <span class="n">IResult</span><span class="p">,</span> <span class="n">IEndpointMetadataProvider</span><span class="p">,</span> <span class="n">IStatusCodeHttpResult</span><span class="p">,</span> <span class="n">IValueHttpResult</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">TResult</span> <span class="n">_result</span> <span class="p">=</span> <span class="n">result</span><span class="p">;</span>

    <span class="c1">// ...</span>

    <span class="k">public</span> <span class="n">Task</span> <span class="nf">ExecuteAsync</span><span class="p">(</span><span class="n">HttpContext</span> <span class="n">httpContext</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_result</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">httpContext</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">StatusCode</span> <span class="p">=</span> <span class="n">StatusCodes</span><span class="p">.</span><span class="n">Status204NoContent</span><span class="p">;</span>
            <span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// 👇 Get negotiator based on Accept header</span>
        <span class="kt">var</span> <span class="n">negotiator</span> <span class="p">=</span> <span class="nf">GetNegotiator</span><span class="p">(</span><span class="n">httpContext</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">negotiator</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">httpContext</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">StatusCode</span> <span class="p">=</span> <span class="n">StatusCodes</span><span class="p">.</span><span class="n">Status406NotAcceptable</span><span class="p">;</span>
            <span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// 👇 Set status code</span>
        <span class="n">httpContext</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">StatusCode</span> <span class="p">=</span> <span class="n">StatusCode</span><span class="p">;</span>

        <span class="c1">// 👇 Handle the result</span>
        <span class="k">return</span> <span class="n">negotiator</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="n">httpContext</span><span class="p">,</span> <span class="n">_result</span><span class="p">,</span> <span class="n">httpContext</span><span class="p">.</span><span class="n">RequestAborted</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">static</span> <span class="n">IResponseNegotiator</span><span class="p">?</span> <span class="nf">GetNegotiator</span><span class="p">(</span><span class="n">HttpContext</span> <span class="n">httpContext</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">accept</span> <span class="p">=</span> <span class="n">httpContext</span><span class="p">.</span><span class="n">Request</span><span class="p">.</span><span class="nf">GetTypedHeaders</span><span class="p">().</span><span class="n">Accept</span><span class="p">;</span>
        <span class="c1">// 👇 Get negotiator based on Accept header (use ContentNegotiationProvider)</span>
        <span class="k">return</span> <span class="n">ContentNegotiationProvider</span><span class="p">.</span><span class="n">Negotiators</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">n</span> <span class="p">=&gt;</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="n">accept</span><span class="p">.</span><span class="nf">Any</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="n">n</span><span class="p">.</span><span class="nf">CanHandle</span><span class="p">(</span><span class="n">a</span><span class="p">));</span>
        <span class="p">});</span>
    <span class="p">}</span>

    <span class="c1">//...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>To make the documentation nicely generated and contain information about possible formats, we can implement <code class="highlighter-rouge">IEndpointMetadataProvider</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">void</span> <span class="n">IEndpointMetadataProvider</span><span class="p">.</span><span class="nf">PopulateMetadata</span><span class="p">(</span><span class="n">MethodInfo</span> <span class="n">method</span><span class="p">,</span> <span class="n">EndpointBuilder</span> <span class="n">builder</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 👇 Add produces response type metadata</span>
    <span class="n">builder</span><span class="p">.</span><span class="n">Metadata</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">ProducesResponseTypeMetadata</span><span class="p">(</span><span class="n">StatusCodes</span><span class="p">.</span><span class="n">Status200OK</span><span class="p">,</span> <span class="k">typeof</span><span class="p">(</span><span class="n">TResult</span><span class="p">),</span>
        <span class="n">ContentNegotiationProvider</span><span class="p">.</span><span class="n">Negotiators</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">n</span> <span class="p">=&gt;</span> <span class="n">n</span><span class="p">.</span><span class="n">ContentType</span><span class="p">).</span><span class="nf">ToArray</span><span class="p">()));</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="helper-method">Helper method</h2>

<p>Let’s create a static helper for ease of use.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">Negotiation</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">ContentNegotiationResult</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Negotiate</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">T</span> <span class="n">result</span><span class="p">)</span>
        <span class="p">=&gt;</span> <span class="k">new</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="usage">Usage</h2>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">MapGet</span><span class="p">(</span><span class="s">"/products"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="c1">// 👇 Use Negotiation</span>
    <span class="k">return</span> <span class="n">Negotiation</span><span class="p">.</span><span class="nf">Negotiate</span><span class="p">(</span><span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;()</span> <span class="p">{</span> <span class="k">new</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="s">"Product 1"</span><span class="p">,</span> <span class="m">100</span><span class="p">)</span> <span class="p">});</span>
<span class="p">});</span>

<span class="n">app</span><span class="p">.</span><span class="nf">MapGet</span><span class="p">(</span><span class="s">"/products/{id}"</span><span class="p">,</span> <span class="n">GetProduct</span><span class="p">);</span>

<span class="k">static</span> <span class="n">Results</span><span class="p">&lt;</span><span class="n">ContentNegotiationResult</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;,</span> <span class="n">NotFound</span><span class="p">&gt;</span> <span class="nf">GetProduct</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="p">==</span> <span class="m">1</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 👇 Use Negotiation</span>
        <span class="k">return</span> <span class="n">Negotiation</span><span class="p">.</span><span class="nf">Negotiate</span><span class="p">(</span><span class="k">new</span> <span class="nf">Product</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="s">"Product 1"</span><span class="p">,</span> <span class="m">100</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="k">else</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">TypedResults</span><span class="p">.</span><span class="nf">NotFound</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That’s it ✅.</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">### GET products as XML
GET http://localhost:5210/product/
Accept: application/xml

### Response
&lt;ArrayOfProduct xmlns="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"&gt;
  &lt;Product&gt;
    &lt;Id&gt;1&lt;/Id&gt;
    &lt;Name&gt;Product 1&lt;/Name&gt;
    &lt;Price&gt;100&lt;/Price&gt;
  &lt;/Product&gt;
&lt;/ArrayOfProduct&gt;
</span></code></pre></div></div>

<blockquote>
  <p>⚠️ This solution is just a basic suggestion and does not cover all possible scenarios. 
For example, the way it is done you can only use it for <code class="highlighter-rouge">200 OK</code> answers (but it can be extended).</p>
</blockquote>

<p>The full example is on <a href="https://github.com/Burgyn/Samples.ContentNegotion">GitHub</a>.</p>]]></content><author><name>Milan Martiniak</name><email>mino.martiniak@gmail.com</email></author><category term="csharp" /><category term="asp.net core" /><summary type="html"><![CDATA[A guide to creating your own support for Content Negotiation in ASP.NET Core Minimal API by developing a custom 'IResult' implementation.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.burgyn.online/assets/images/code_images/asp-net-core-minimal-api-content-negotiation/cover.png" /><media:content medium="image" url="https://blog.burgyn.online/assets/images/code_images/asp-net-core-minimal-api-content-negotiation/cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>