<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>2025 on Funky Si's Blog (Dev)</title><link>https://blog-dev.funkysi1701.com/2025/</link><description>Recent content in 2025 on Funky Si's Blog (Dev)</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><managingEditor>funkysi1701@gmail.com (Simon Foster)</managingEditor><webMaster>funkysi1701@gmail.com (Simon Foster)</webMaster><lastBuildDate>Fri, 12 Dec 2025 10:00:00 +0000</lastBuildDate><atom:link href="https://blog-dev.funkysi1701.com/2025/index.xml" rel="self" type="application/rss+xml"/><item><title>A festive naughty or nice checker</title><link>https://blog-dev.funkysi1701.com/posts/2025/festive-naughty-or-nice-checker/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Fri, 12 Dec 2025 10:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/festive-naughty-or-nice-checker/</guid><category term=".NET">.NET</category><category term="SemanticKernel">SemanticKernel</category><category term="AI">AI</category><category term="Christmas">Christmas</category><category term="festive">festive</category><category term="naughtyornice">naughtyornice</category><category term="C-Sharp">C-Sharp</category><category term="MachineLearning">MachineLearning</category><category term="tutorial">tutorial</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/2025/festive-tech-calendar.png"/><description>&lt;p&gt;
&lt;img class="img-fluid" alt="Festive Tech Calendar" src="https://blog-dev.funkysi1701.com/images/2025/festive-tech-calendar.png" loading="lazy"
width="817" height="545"
/&gt;
&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="anchor ms-1" href="#introduction" aria-label="Permalink: Introduction"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m excited to be taking part in this year&amp;rsquo;s &lt;a href="https://festivetechcalendar.com/" target="_blank" rel="noopener noreferrer"&gt;Festive Tech Calendar&lt;/a&gt;
, a community-driven advent calendar showcasing amazing technical content throughout December. Make sure to check out the other excellent posts created by developers from around the world!&lt;/p&gt;
&lt;p&gt;In this festive tutorial, we&amp;rsquo;ll build a fun &amp;ldquo;Naughty or Nice&amp;rdquo; checker using Microsoft&amp;rsquo;s Semantic Kernel and .NET. This playful AI-powered tool analyzes text descriptions and delivers a verdict on whether someone has been naughty or nice this year—perfect for adding some holiday cheer to your portfolio! Whether you&amp;rsquo;re new to AI integration or looking to explore Semantic Kernel, this beginner-friendly guide will walk you through creating a complete working application from scratch. By the end, you&amp;rsquo;ll have a deployable web app and understand how to integrate large language models into your .NET projects.&lt;/p&gt;
&lt;h2 id="my-take-on-ai"&gt;My Take on AI&lt;a class="anchor ms-1" href="#my-take-on-ai" aria-label="Permalink: My Take on AI"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before diving into code, I want to share a quick perspective on using AI in development. While building this demo, I used &lt;a href="https://github.com/copilot" target="_blank" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt;
to transform a basic UI into something more festive.&lt;/p&gt;
&lt;p&gt;Starting with a simple, functional interface:&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="boring looking application" src="https://blog-dev.funkysi1701.com/images/2025/before.png" loading="lazy"
width="1487" height="347"
/&gt;
&lt;/p&gt;
&lt;p&gt;After a few prompts to Copilot, I had a Christmas-themed demo with loading spinners and festive icons:&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Christmas version" src="https://blog-dev.funkysi1701.com/images/2025/after.png" loading="lazy"
width="1128" height="432"
/&gt;
&lt;/p&gt;
&lt;p&gt;Could I have coded this myself? Absolutely. But I&amp;rsquo;d rather focus my energy on the backend Semantic Kernel integration than wrestling with CSS animations. &lt;strong&gt;This is where AI shines—handling the tedious parts so you can focus on the interesting problems.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="the-reality-check"&gt;The Reality Check&lt;a class="anchor ms-1" href="#the-reality-check" aria-label="Permalink: The Reality Check"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;That said, AI isn&amp;rsquo;t magic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hallucinations happen&lt;/strong&gt; - I&amp;rsquo;ve lost time chasing incorrect suggestions, so don&amp;rsquo;t retire your search engine just yet&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ownership questions remain&lt;/strong&gt; - Who owns AI-generated code? This is still being figured out&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s a tool, not a replacement&lt;/strong&gt; - AI augments developers; it doesn&amp;rsquo;t replace critical thinking&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My approach: Use AI as a productivity multiplier, but always verify, understand, and own what you ship.&lt;/p&gt;
&lt;h2 id="what-is-semantic-kernel"&gt;What is Semantic Kernel?&lt;a class="anchor ms-1" href="#what-is-semantic-kernel" aria-label="Permalink: What is Semantic Kernel?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Semantic Kernel is an open-source SDK from Microsoft that makes it easy to integrate AI models into your .NET applications. It allows you to use large language models (LLMs) for tasks like text analysis, summarization, and more.&lt;/p&gt;
&lt;h2 id="project-overview"&gt;Project Overview&lt;a class="anchor ms-1" href="#project-overview" aria-label="Permalink: Project Overview"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ll create a simple web app that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accepts a name or short description of someone&amp;rsquo;s behaviour&lt;/li&gt;
&lt;li&gt;Uses Semantic Kernel to analyze the input&lt;/li&gt;
&lt;li&gt;Returns a festive verdict: Naughty or Nice&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;a class="anchor ms-1" href="#prerequisites" aria-label="Permalink: Prerequisites"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;.NET 10 (I am not using any .NET 10 specific features so could probably be rewritten with older versions)&lt;/li&gt;
&lt;li&gt;Semantic Kernel (NuGet package)&lt;/li&gt;
&lt;li&gt;An OpenAI API key - ⚠️ &lt;strong&gt;Note&lt;/strong&gt;: API usage costs ~$0.01-0.05 per request&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="openai-and-costs"&gt;OpenAI and Costs&lt;a class="anchor ms-1" href="#openai-and-costs" aria-label="Permalink: OpenAI and Costs"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For this example I have used an OpenAI API Key. This is not free, however while writing this example I have only spent a few pounds in tokens. For more information about OpenAI head over to &lt;a href="https://platform.openai.com/" target="_blank" rel="noopener noreferrer"&gt;https://platform.openai.com/&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;For this reason I have not provided a live demo, (I don&amp;rsquo;t want you all using my API key), and no API keys are included in my example code.&lt;/p&gt;
&lt;h2 id="step-1-setting-up-the-project"&gt;Step 1: Setting Up the Project&lt;a class="anchor ms-1" href="#step-1-setting-up-the-project" aria-label="Permalink: Step 1: Setting Up the Project"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create a new Blazor Web App:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new blazor -n NaughtyOrNiceChecker
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd NaughtyOrNiceChecker
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Install the required NuGet packages: (Microsoft.SemanticKernel.Connectors.OpenAI Version 1.67.1 as I write this)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add your OpenAI configuration to appsettings.json:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;OpenAI&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Key&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;your-api-key-here&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// Head over to https://platform.openai.com/settings/organization/api-keys
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;gpt-5&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;// I am using gpt-5 but feel free to experiment with other models
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🔒 &lt;strong&gt;Security&lt;/strong&gt;: Never commit API keys to GitHub. Use user secrets or environment variables.&lt;/p&gt;
&lt;p&gt;The code for my project can be found on GitHub at &lt;a href="https://github.com/funkysi1701/FestiveTechCalendar2025" target="_blank" rel="noopener noreferrer"&gt;https://github.com/funkysi1701/FestiveTechCalendar2025&lt;/a&gt;
.&lt;/p&gt;
&lt;h2 id="step-2-integrate-semantic-kernel"&gt;Step 2: Integrate Semantic Kernel&lt;a class="anchor ms-1" href="#step-2-integrate-semantic-kernel" aria-label="Permalink: Step 2: Integrate Semantic Kernel"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Configure Semantic Kernel to use your LLM provider (OpenAI). In the &lt;code&gt;Program.cs&lt;/code&gt; file you will see the following code, this loads the OpenAI key and model from your &lt;code&gt;appsettings.json&lt;/code&gt; config file, and allows it to be used by Semantic Kernel.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; key = builder.Configuration.GetValue&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;OpenAI:Key&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; model = builder.Configuration.GetValue&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;OpenAI:Model&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrEmpty(key))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.Services.AddOpenAIChatCompletion(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; modelId: model ?? &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ApplicationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Missing OpenAI Config&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apiKey: key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddKernel();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-3-create-the-blazor-component"&gt;Step 3: Create the Blazor Component&lt;a class="anchor ms-1" href="#step-3-create-the-blazor-component" aria-label="Permalink: Step 3: Create the Blazor Component"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create a new file &lt;code&gt;Components/Pages/NaughtyOrNice.razor&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@page &lt;span style="color:#e6db74"&gt;&amp;#34;/naughty-or-nice&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@inject Kernel _kernel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@rendermode InteractiveServer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PageTitle&amp;gt;Naughty or Nice Checker&amp;lt;/PageTitle&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;h3&amp;gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;🎅&lt;/span&gt; Naughty or Nice Checker&amp;lt;/h3&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;div class=&lt;span style="color:#e6db74"&gt;&amp;#34;card&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&lt;span style="color:#e6db74"&gt;&amp;#34;card-body&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input @bind=&lt;span style="color:#e6db74"&gt;&amp;#34;input&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; placeholder=&lt;span style="color:#e6db74"&gt;&amp;#34;Describe their behavior...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; class=&lt;span style="color:#e6db74"&gt;&amp;#34;form-control mb-3&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;button @onclick=&lt;span style="color:#e6db74"&gt;&amp;#34;CheckStatus&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; class=&lt;span style="color:#e6db74"&gt;&amp;#34;btn btn-primary&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Check Status
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @if (!&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrEmpty(response))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&lt;span style="color:#e6db74"&gt;&amp;#34;alert alert-@alertClass mt-3&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;h4&amp;gt;@response&amp;lt;/h4&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@code {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; childsName = &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; response = &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; alertClass =&amp;gt; response == &lt;span style="color:#e6db74"&gt;&amp;#34;Naughty&amp;#34;&lt;/span&gt; ? &lt;span style="color:#e6db74"&gt;&amp;#34;danger&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;success&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; isLoading = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task CheckStatus()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; isLoading = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; InvokeAsync(StateHasChanged);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response = &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; input = (childsName).Trim();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrEmpty(input))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var cts = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; CancellationTokenSource(TimeSpan.FromSeconds(&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; prompt = &lt;span style="color:#e6db74"&gt;&amp;#34;You are Santa&amp;#39;s assistant. Given the child&amp;#39;s name or short description, &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;decide if they are Naughty or Nice for Christmas. &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Respond only with one word: Naughty or Nice. &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;No punctuation, no explanation. Input: {{$input}}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; classifyFunc = _kernel.CreateFunctionFromPrompt(prompt);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; aiResponse = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _kernel.InvokeAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; classifyFunc,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [&amp;#34;input&amp;#34;]&lt;/span&gt; = input
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cts.Token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; text = aiResponse.GetValue&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt;()?.Trim();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Normalize and enforce strict output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Equals(text, &lt;span style="color:#e6db74"&gt;&amp;#34;naughty&amp;#34;&lt;/span&gt;, StringComparison.OrdinalIgnoreCase))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response = &lt;span style="color:#e6db74"&gt;&amp;#34;Naughty&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Equals(text, &lt;span style="color:#e6db74"&gt;&amp;#34;nice&amp;#34;&lt;/span&gt;, StringComparison.OrdinalIgnoreCase))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response = &lt;span style="color:#e6db74"&gt;&amp;#34;Nice&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Fallback: default to Naughty to avoid leaking other content&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response = &lt;span style="color:#e6db74"&gt;&amp;#34;Naughty&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;finally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; isLoading = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; InvokeAsync(StateHasChanged);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note: The _kernel is injected automatically because we registered it in Program.cs with builder.Services.AddKernel().&lt;/p&gt;
&lt;h2 id="step-4-crafting-the-perfect-prompt"&gt;Step 4: Crafting the Perfect Prompt&lt;a class="anchor ms-1" href="#step-4-crafting-the-perfect-prompt" aria-label="Permalink: Step 4: Crafting the Perfect Prompt"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Getting AI to return exactly what you want requires iteration. I&amp;rsquo;ll walk you through my prompt engineering journey from verbose outputs to production-ready responses.&lt;/p&gt;
&lt;h3 id="iteration-1-too-verbose"&gt;Iteration 1: Too Verbose&lt;a class="anchor ms-1" href="#iteration-1-too-verbose" aria-label="Permalink: Iteration 1: Too Verbose"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; aiResponse = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _kernel.InvokePromptAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;$&amp;#34;You are a tool to help Santa identify if children have been naughty or nice. &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;$&amp;#34;Has {input} been naughty or nice this year?&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cancellationToken: cancellationToken.Token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Santa&lt;/td&gt;
&lt;td&gt;Short answer: Nice. Why: - Spreads joy and generosity globally - Works hard with his team, treats elves and reindeer well - Answers letters and keeps promises (on-time deliveries) Minor demerits (lighthearted): - Excess cookie consumption - Occasional chimney soot prints Verdict: Firmly on the Nice List. Want a fun “Official Nice Report” for Santa you can share?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Darth Vader&lt;/td&gt;
&lt;td&gt;Not exactly a child, but Santa checks everyone. Darth Vader’s record this year includes Force-choking subordinates and aiding Imperial oppression—firmly Naughty. Mitigating note: he did protect his son and turn against the Emperor, which earns a small mercy. Final verdict: Naughty list (with a tiny redemption candy cane).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;James&lt;/td&gt;
&lt;td&gt;I don’t have access to Santa’s list or personal data, so I can’t say for sure about James. But I can help you decide with a quick Naughty/Nice check: Score each item: - Kind and helpful to family/classmates most of the time? (Yes +2, Sometimes +1, No 0) - Tells the truth and takes responsibility? (Yes +2, Sometimes +1, No 0) - Apologizes and makes things right after mistakes? (Yes +2, Sometimes +1, No 0) - Respects rules and others (no bullying or deliberate harm)? (Yes +2, Sometimes +1, No 0; deduct 2 if there’s repeated serious harm) - Does generous things (sharing, chores, helping the community)? (Yes +2, Sometimes +1, No 0) Totals: - 8–10: Nice List - 5–7: Mostly Nice (keep it up) - 2–4: Mixed (needs a December boost) - 0–1 or repeated serious harm: Naughty-for-now (with room to improve) If James needs a boost: make amends where needed, do one unprompted kindness or chore daily, be honest about mistakes right away, and share/donate something or help someone weekly. Share a few examples of James’s year and I’ll help you make the call.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skeletor&lt;/td&gt;
&lt;td&gt;Verdict: Naughty. Why: - Repeated attempts to conquer Eternia and seize Castle Grayskull - Habitual scheming, lying, and bullying his own minions - Frequent unleashing of dangerous magic on innocents Path back to the Nice List: - Call a truce with He-Man and protect Eternia for a season - Return stolen artifacts and undo harmful spells - Treat his minions fairly and do a few good deeds without expecting power in return&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is far too wordy, I really just want a Naughty or Nice response.&lt;/p&gt;
&lt;h3 id="iteration-2-constraining-the-output"&gt;Iteration 2: Constraining the Output&lt;a class="anchor ms-1" href="#iteration-2-constraining-the-output" aria-label="Permalink: Iteration 2: Constraining the Output"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Then I tried:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; aiResponse = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _kernel.InvokePromptAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;$&amp;#34;Given the following description, decide if the person is Naughty or Nice for Christmas. &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;$&amp;#34;Respond only with &amp;#39;Naughty&amp;#39; or &amp;#39;Nice&amp;#39;. &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;$&amp;#34;Description: {input}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cancellationToken: cancellationToken.Token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Santa&lt;/td&gt;
&lt;td&gt;Nice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Darth Vader&lt;/td&gt;
&lt;td&gt;Naughty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;James&lt;/td&gt;
&lt;td&gt;Nice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skeletor&lt;/td&gt;
&lt;td&gt;Naughty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is better but every name I tried is giving me a Nice, so let&amp;rsquo;s see if we can try something a bit more clever. However if instead of only including a name, we include some naughty or nice actions we get some interesting results.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Santa delivered some presents&lt;/td&gt;
&lt;td&gt;Nice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Darth Vader destroyed Alderaan&lt;/td&gt;
&lt;td&gt;Naughty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;James tidied his room&lt;/td&gt;
&lt;td&gt;Nice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skeletor got cross with Evil Lynn&lt;/td&gt;
&lt;td&gt;Naughty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="version-3-non-ai-approach-plugin-example"&gt;Version 3: Non-AI Approach (Plugin Example)&lt;a class="anchor ms-1" href="#version-3-non-ai-approach-plugin-example" aria-label="Permalink: Version 3: Non-AI Approach (Plugin Example)"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This version demonstrates Semantic Kernel&amp;rsquo;s plugin system without using AI. While it doesn&amp;rsquo;t actually help Santa (it just generates a hash-based result), it shows how you can integrate traditional .NET code into Semantic Kernel workflows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why show this?&lt;/strong&gt; To illustrate that Semantic Kernel isn&amp;rsquo;t just for AI—you can mix AI calls with regular code execution in the same pipeline.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; aiResponse = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _kernel.InvokeAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pluginName,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GenerateHash&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; KernelArguments
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [&amp;#34;input&amp;#34;]&lt;/span&gt; = input
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This version invokes a semantic kernel plugin function called GenerateHash. The function itself is defined in a separate class with a KernelFunction attribute, it is really simple just generates a hash from the input string. This is just an example of how you can call .NET code from Semantic Kernel. The kernel function requires an extra line to wire up correctly I have placed this in the OnInitialized method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; OnInitialized()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; rngPlugin = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ExamplePlugin();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _kernel.ImportPluginFromObject(rngPlugin, pluginName);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ExamplePlugin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [KernelFunction]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; GenerateHash(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; input)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (input == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentNullException(nameof(input));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; hash = input.GetHashCode();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; hash &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; ? &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; : &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="final-version-production-ready-implementation"&gt;Final Version: Production-Ready Implementation&lt;a class="anchor ms-1" href="#final-version-production-ready-implementation" aria-label="Permalink: Final Version: Production-Ready Implementation"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;My last example is very similar to Version 2 but let&amp;rsquo;s have a look at it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; prompt = &lt;span style="color:#e6db74"&gt;&amp;#34;You are Santa&amp;#39;s assistant. Given the child&amp;#39;s name or short description, &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;decide if they are Naughty or Nice for Christmas. Respond only with one word: Naughty or Nice. &amp;#34;&lt;/span&gt; +
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;No punctuation, no explanation. Input: {{$input}}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; classifyFunc = _kernel.CreateFunctionFromPrompt(prompt);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; aiResponse = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _kernel.InvokeAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; classifyFunc,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [&amp;#34;input&amp;#34;]&lt;/span&gt; = input
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cancellationToken.Token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we have a bit more error checking to make sure we have entered a valid string before calling OpenAI. This time we use the CreateFunctionFromPrompt method to pass in a prompt, we then invoke the prompt along with the string that has been entered.&lt;/p&gt;
&lt;p&gt;This time we check for naughty or nice in the response before returning that to the user, if neither is returned we return Naughty.&lt;/p&gt;
&lt;p&gt;This works quite well if we enter a more descriptive input.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Santa delivered some presents&lt;/td&gt;
&lt;td&gt;Nice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Darth Vader destroyed Alderaan&lt;/td&gt;
&lt;td&gt;Naughty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;James tidied his room&lt;/td&gt;
&lt;td&gt;Nice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skeletor got cross with Evil Lynn&lt;/td&gt;
&lt;td&gt;Naughty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As you can see if we describe the actions of someone we can categorize them as naughty or nice, which is basically what Santa does, so this tool should be a great help to him.&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Is Santa Naughty or Nice?" src="https://blog-dev.funkysi1701.com/images/2025/working-naughty-nice.png" loading="lazy"
width="1169" height="618"
/&gt;
&lt;/p&gt;
&lt;h3 id="comparing-all-versions"&gt;Comparing All Versions&lt;a class="anchor ms-1" href="#comparing-all-versions" aria-label="Permalink: Comparing All Versions"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Open-ended prompt&lt;/td&gt;
&lt;td&gt;Rich explanations&lt;/td&gt;
&lt;td&gt;Too verbose&lt;/td&gt;
&lt;td&gt;Learning/debugging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Constrained output&lt;/td&gt;
&lt;td&gt;Concise&lt;/td&gt;
&lt;td&gt;Needs context&lt;/td&gt;
&lt;td&gt;Simple classifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Hash-based (no AI)&lt;/td&gt;
&lt;td&gt;No API costs&lt;/td&gt;
&lt;td&gt;Not intelligent&lt;/td&gt;
&lt;td&gt;Plugin example only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Validated prompts&lt;/td&gt;
&lt;td&gt;Reliable, safe&lt;/td&gt;
&lt;td&gt;More code&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this tutorial, we built a festive AI-powered application using Semantic Kernel and .NET. Here&amp;rsquo;s what we accomplished:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Integrated OpenAI with Semantic Kernel in a Blazor app&lt;/li&gt;
&lt;li&gt;✅ Learned prompt engineering through iteration (4 versions!)&lt;/li&gt;
&lt;li&gt;✅ Implemented proper error handling and output validation&lt;/li&gt;
&lt;li&gt;✅ Created a fun, interactive holiday application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have tried to highlight some of the different ways that you can use Semantic Kernel that you could use in your own projects. Some of my examples could be accomplished without the use of Semantic Kernel, for example randomly generating Naughty and Nice doesn&amp;rsquo;t need AI, however hopefully you can see that getting results from AI can then be augmented using traditional .NET code to execute other functions.&lt;/p&gt;
&lt;h3 id="key-takeaways"&gt;Key Takeaways&lt;a class="anchor ms-1" href="#key-takeaways" aria-label="Permalink: Key Takeaways"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prompt engineering matters&lt;/strong&gt; - Notice how Version 4&amp;rsquo;s explicit instructions produced better results&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation is essential&lt;/strong&gt; - Always sanitize LLM outputs before displaying to users&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep it simple&lt;/strong&gt; - Complex doesn&amp;rsquo;t mean better (Version 3&amp;rsquo;s random approach was overkill)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cost-considerations"&gt;Cost Considerations&lt;a class="anchor ms-1" href="#cost-considerations" aria-label="Permalink: Cost Considerations"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;During development, I spent only a few pounds on API tokens. For production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use Azure OpenAI for predictable pricing&lt;/li&gt;
&lt;li&gt;Implement caching for common queries&lt;/li&gt;
&lt;li&gt;Set up rate limiting to prevent abuse&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="deployment-options"&gt;Deployment Options&lt;a class="anchor ms-1" href="#deployment-options" aria-label="Permalink: Deployment Options"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This Blazor app can be deployed to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Azure Static Web Apps&lt;/strong&gt; (free tier includes 2 custom domains)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure App Service&lt;/strong&gt; (full .NET hosting)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker containers&lt;/strong&gt; (run anywhere)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="next-steps"&gt;Next Steps&lt;a class="anchor ms-1" href="#next-steps" aria-label="Permalink: Next Steps"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Want to extend this project? Try:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a database to track naughty/nice history&lt;/li&gt;
&lt;li&gt;Implement user authentication&lt;/li&gt;
&lt;li&gt;Deploy to Azure Static Web Apps&lt;/li&gt;
&lt;li&gt;Add more context (age, location) for better AI decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy coding and Merry Christmas! 🎄&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/semantic-kernel" target="_blank" rel="noopener noreferrer"&gt;Semantic Kernel GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/" target="_blank" rel="noopener noreferrer"&gt;Microsoft Semantic Kernel Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.openai.com/docs/api-reference" target="_blank" rel="noopener noreferrer"&gt;OpenAI API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/" target="_blank" rel="noopener noreferrer"&gt;Azure OpenAI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Blazor and .NET 10 breaking changes</title><link>https://blog-dev.funkysi1701.com/posts/2025/blazor-and-dotnet10/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 17 Nov 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/blazor-and-dotnet10/</guid><category term="Blazor">Blazor</category><category term=".NET10">.NET10</category><category term="WebAssembly">WebAssembly</category><category term="Wasm">Wasm</category><category term="HttpClient">HttpClient</category><category term="breakingchange">breakingchange</category><category term="DotNet">DotNet</category><category term="VisualStudio">VisualStudio</category><category term="release">release</category><category term="streamingresponse">streamingresponse</category><category term="C-Sharp">C-Sharp</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/2025/Android%20Small.png"/><description>&lt;h2 id="whats-new-in-net-10"&gt;What’s New in .NET 10?&lt;a class="anchor ms-1" href="#whats-new-in-net-10" aria-label="Permalink: What’s New in .NET 10?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;.NET 10 was released this week and it is full of awesome new features. Check it out at &lt;a href="https://dotnet.microsoft.com/download/dotnet/10.0" target="_blank" rel="noopener noreferrer"&gt;.NET Download&lt;/a&gt;
, there is also a new version of &lt;a href="https://visualstudio.microsoft.com/" target="_blank" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt;
which you should also check out.&lt;/p&gt;
&lt;p&gt;I quickly upgraded my .NET projects to take advantage of the new version, but my Blazor WebAssembly (Wasm) project encountered the following error in the browser console:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;System.NotSupportedException: net_http_synchronous_reads_not_supported
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at System.Net.Http.BrowserHttpReadStream.Read(Byte[] buffer, Int32 offset, Int32 count)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at System.IO.DelegatingStream.Read(Byte[] buffer, Int32 offset, Int32 count)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at System.IO.StreamReader.ReadBuffer(Span`1 userBuffer, Boolean&amp;amp; readToUserBuffer)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at System.IO.StreamReader.ReadSpan(Span`1 buffer)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at System.IO.StreamReader.Read(Char[] buffer, Int32 index, Int32 count)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonTextReader.ReadData(Boolean append, Int32 charsRequired)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonTextReader.ReadData(Boolean append)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonTextReader.ParseValue()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonTextReader.Read()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonReader.ReadAndMoveToContent()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Newtonsoft.Json.JsonSerializer.Deserialize[MyResult](JsonReader reader)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at MyAPI.Client.MyClient.&amp;lt;ReadObjectResponseAsync&amp;gt;d__57`1[[MyAPI.Client.MyResult, MyAPI.Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at MyAPI.Client.MyClient.MyAsync(MyRequest body, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This error was getting thrown on a Blazor Wasm page when it tried to make a call to my API. This worked fine in .NET 9 so my suspicion was this might be a breaking change.&lt;/p&gt;
&lt;p&gt;The list of new features for .NET 10 can be found &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0?view=aspnetcore-9.0" target="_blank" rel="noopener noreferrer"&gt;.NET 10 Release Notes&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id="httpclient-response-streaming-breaking-change-in-blazor"&gt;HttpClient Response Streaming: Breaking Change in Blazor&lt;a class="anchor ms-1" href="#httpclient-response-streaming-breaking-change-in-blazor" aria-label="Permalink: HttpClient Response Streaming: Breaking Change in Blazor"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I found the following new feature which I suspect is related to my issue:&lt;/p&gt;
&lt;p&gt;HttpClient response streaming enabled by default&lt;/p&gt;
&lt;p&gt;In prior Blazor releases, response streaming for HttpClient requests was opt-in. Now, response streaming is enabled by default.&lt;/p&gt;
&lt;p&gt;This is a breaking change because calling &lt;code&gt;HttpContent.ReadAsStreamAsync&lt;/code&gt; for an &lt;code&gt;HttpResponseMessage.Content&lt;/code&gt; (&lt;code&gt;response.Content.ReadAsStreamAsync()&lt;/code&gt;) returns a &lt;code&gt;BrowserHttpReadStream&lt;/code&gt; and no longer a &lt;code&gt;MemoryStream&lt;/code&gt;. &lt;code&gt;BrowserHttpReadStream&lt;/code&gt; doesn&amp;rsquo;t support synchronous operations, such as &lt;code&gt;Stream.Read(Span&amp;lt;Byte&amp;gt;)&lt;/code&gt;. If your code uses synchronous operations, you can opt-out of response streaming or copy the &lt;code&gt;Stream&lt;/code&gt; into a &lt;code&gt;MemoryStream&lt;/code&gt; yourself.&lt;/p&gt;
&lt;p&gt;To opt-out of response streaming globally, use either of the following approaches:&lt;/p&gt;
&lt;p&gt;Add the &amp;lt;WasmEnableStreamingResponse&amp;gt; property to the project file with a value of false:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;WasmEnableStreamingResponse&amp;gt;&lt;/span&gt;false&lt;span style="color:#f92672"&gt;&amp;lt;/WasmEnableStreamingResponse&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Set the DOTNET_WASM_ENABLE_STREAMING_RESPONSE environment variable to false or 0.&lt;/p&gt;
&lt;p&gt;To opt-out of response streaming for an individual request, set &lt;code&gt;SetBrowserResponseStreamingEnabled&lt;/code&gt; to false on the &lt;code&gt;HttpRequestMessage&lt;/code&gt; (&lt;code&gt;requestMessage&lt;/code&gt; in the following example):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;requestMessage.SetBrowserResponseStreamingEnabled(&lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For more information, see HttpClient and HttpRequestMessage with Fetch API request options (Call web API article).&lt;/p&gt;
&lt;h2 id="how-to-fix-the-streaming-issue-in-blazor"&gt;How to Fix the Streaming Issue in Blazor&lt;a class="anchor ms-1" href="#how-to-fix-the-streaming-issue-in-blazor" aria-label="Permalink: How to Fix the Streaming Issue in Blazor"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To test out if this was the culprit I added &lt;code&gt;&amp;lt;WasmEnableStreamingResponse&amp;gt;false&amp;lt;/WasmEnableStreamingResponse&amp;gt;&lt;/code&gt; to my csproj file and sure enough this fixed my issue.&lt;/p&gt;
&lt;p&gt;Do check out the docs for a complete list of new features and any other potential breaking changes. &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0?view=aspnetcore-10.0" target="_blank" rel="noopener noreferrer"&gt;.NET 10 Release Notes&lt;/a&gt;
&lt;/p&gt;</description></item><item><title>Periodic Table of DevOps 2025</title><link>https://blog-dev.funkysi1701.com/posts/2025/periodic-table-devops-2025/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 29 Sep 2025 20:00:45 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/periodic-table-devops-2025/</guid><category term="DevOps">DevOps</category><category term="Tools">Tools</category><category term="Cloud">Cloud</category><category term="containerization">containerization</category><category term="SourceControl">SourceControl</category><category term="Automation">Automation</category><category term="2025">2025</category><category term="2016">2016</category><category term="comparison">comparison</category><category term="trends">trends</category><category term="SoftwareDevelopment">SoftwareDevelopment</category><category term="periodictable">periodictable</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/2025/periodic-table-devops-2025.png"/><description>&lt;p&gt;DevOps has transformed the way teams build, deploy, and manage software. Over the years, the tools we use have evolved rapidly—some have become industry standards, while others have faded away or emerged to solve new challenges. In this post, I revisit the concept of a &amp;ldquo;Periodic Table of DevOps&amp;rdquo; to compare the landscape in 2016 with 2025, highlight key changes, and reflect on what these shifts mean for developers, IT professionals, and organizations embracing modern workflows.&lt;/p&gt;
&lt;p&gt;The periodic table lists all the chemical elements and groups them together based on some key properties. In 2016 I found an article about the &lt;a href="https://digital.ai/periodic-table-of-devops-tools/" target="_blank" rel="noopener noreferrer"&gt;periodic table of DevOps&lt;/a&gt;
and wrote a blog &lt;a href="https://blog-dev.funkysi1701.com/posts/2016/periodic-table-devops/"&gt;post&lt;/a&gt;
about it.&lt;/p&gt;
&lt;h2 id="periodic-table-of-devops-tools-2025"&gt;Periodic Table of DevOps Tools 2025&lt;a class="anchor ms-1" href="#periodic-table-of-devops-tools-2025" aria-label="Permalink: Periodic Table of DevOps Tools 2025"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Periodic Table of DevOps 2025" src="https://blog-dev.funkysi1701.com/images/2025/periodic-table-devops-2025.png" loading="lazy"
width="1386" height="879"
/&gt;
&lt;/p&gt;
&lt;h2 id="periodic-table-of-devops-tools-2016"&gt;Periodic Table of DevOps Tools 2016&lt;a class="anchor ms-1" href="#periodic-table-of-devops-tools-2016" aria-label="Permalink: Periodic Table of DevOps Tools 2016"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Periodic Table of DevOps 2016" src="https://blog-dev.funkysi1701.com/images/2016/periodic-table-of-devops-v2.png" loading="lazy"
width="4896" height="2734"
/&gt;
&lt;/p&gt;
&lt;p&gt;I thought it might be interesting to see what tools are still in use, and what are new.&lt;/p&gt;
&lt;h2 id="source-control"&gt;Source Control&lt;a class="anchor ms-1" href="#source-control" aria-label="Permalink: Source Control"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Git, the most popular source control system, is on both charts, in 2016 it was at position 3, and in 2025 it is at 2.
GitHub, the most widely used web-based git repository, is also on both charts, moving from 1 to 10.
Bitbucket (now owned by Atlassian) was a hosted git repository I used to use and is still on the chart, moving from 11 to 36.
Subversion, a source control system I started my career with, has vanished off the chart in 2025. Not really a surprise due to the popularity of git now.&lt;/p&gt;
&lt;h2 id="cloud-platforms"&gt;Cloud Platforms&lt;a class="anchor ms-1" href="#cloud-platforms" aria-label="Permalink: Cloud Platforms"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In 2016 we had a whole column of Cloud Platforms, including Rackspace and OpenShift, in 2025 this has been reduced to the big three (Azure, AWS and GCP) plus OpenShift.&lt;/p&gt;
&lt;h2 id="containerization"&gt;Containerization&lt;a class="anchor ms-1" href="#containerization" aria-label="Permalink: Containerization"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In 2016 we had a column of tools like Docker, Docker Swarm and Kubernetes. In 2025 there are far more tools in this space, with the addition of tools like Helm and the various ways you can host containers on cloud platforms (like Azure AKS).&lt;/p&gt;
&lt;h2 id="build-and-deploy-tools"&gt;Build and Deploy tools&lt;a class="anchor ms-1" href="#build-and-deploy-tools" aria-label="Permalink: Build and Deploy tools"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In 2016 Azure DevOps was still called Team Foundation Server, so this has spread out into the various ways you can use Azure DevOps, such as Pipelines and Code. Also since 2016 GitHub Actions has appeared on the market.&lt;/p&gt;
&lt;p&gt;In 2016 I was also dabbling with TeamCity, this is no longer used that much so isn&amp;rsquo;t on the chart in 2025. Jenkins is still around on both charts.&lt;/p&gt;
&lt;h2 id="comms-tools"&gt;Comms Tools&lt;a class="anchor ms-1" href="#comms-tools" aria-label="Permalink: Comms Tools"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In 2025 Slack and MS Teams are the go-to tools for communicating with distributed teams, Slack was around in 2016, but Teams wasn&amp;rsquo;t a thing back then.&lt;/p&gt;
&lt;h2 id="new-tools"&gt;New Tools&lt;a class="anchor ms-1" href="#new-tools" aria-label="Permalink: New Tools"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;New tools for monitoring like Grafana have appeared; back in 2016 I was exclusively using Application Insights from Azure.&lt;/p&gt;
&lt;h2 id="conclusion--key-takeaways"&gt;Conclusion &amp; Key Takeaways&lt;a class="anchor ms-1" href="#conclusion--key-takeaways" aria-label="Permalink: Conclusion &amp;amp; Key Takeaways"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The DevOps landscape has changed dramatically over the past decade. Some tools have stood the test of time, while others have faded or emerged to meet new challenges. Here are the key takeaways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Git remains the backbone of source control&lt;/strong&gt;, with platforms like GitHub and Bitbucket continuing to thrive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud platforms (Azure, AWS, GCP) have become central&lt;/strong&gt;, reflecting the shift to cloud-native development and operations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Containerization and orchestration (Docker, Kubernetes, Helm) are now essential&lt;/strong&gt;, enabling scalable, flexible deployments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring and collaboration tools have evolved&lt;/strong&gt;, with newcomers like Grafana and MS Teams joining established solutions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Legacy tools are disappearing&lt;/strong&gt;, showing the importance of adaptability in tech.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As DevOps continues to evolve, staying curious and open to new tools is key. The periodic table analogy reminds us that the right mix of technologies can empower teams to build, deploy, and manage software more effectively. What tools will shape the next decade? Only time will tell.&lt;/p&gt;</description></item><item><title>The Mandelbrot Set</title><link>https://blog-dev.funkysi1701.com/posts/2025/mandelbrot-set/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 15 Sep 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/mandelbrot-set/</guid><category term="mandelbrotset">mandelbrotset</category><category term="fractals">fractals</category><category term="mathematics">mathematics</category><category term="chaostheory">chaostheory</category><category term="complexnumbers">complexnumbers</category><category term="art">art</category><category term="visualization">visualization</category><category term="mathart">mathart</category><category term="self-similarity">self-similarity</category><category term="complexdynamics">complexdynamics</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/mandelbrot.png"/><description>&lt;h2 id="the-mandelbrot-set-beauty-chaos-and-mathematical-art"&gt;The Mandelbrot Set: Beauty, Chaos, and Mathematical Art&lt;a class="anchor ms-1" href="#the-mandelbrot-set-beauty-chaos-and-mathematical-art" aria-label="Permalink: The Mandelbrot Set: Beauty, Chaos, and Mathematical Art"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="The Mandelbrot set" src="https://blog-dev.funkysi1701.com/images/mandelbrot.png" loading="lazy"
width="800" height="600"
/&gt;
&lt;/p&gt;
&lt;p&gt;The Mandelbrot set is one of the most iconic images in mathematics—a mesmerizing swirl of fractal patterns, infinite complexity, and unexpected beauty. But what exactly is the Mandelbrot set, and why has it captured the imagination of mathematicians, artists, and curious minds alike?&lt;/p&gt;
&lt;h2 id="what-is-the-mandelbrot-set"&gt;What Is the Mandelbrot Set?&lt;a class="anchor ms-1" href="#what-is-the-mandelbrot-set" aria-label="Permalink: What Is the Mandelbrot Set?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At its core, the Mandelbrot set is a collection of complex numbers. It’s defined by a simple equation, but the results are anything but simple. The definition is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A complex number &lt;strong&gt;c&lt;/strong&gt; is in the Mandelbrot set if, when you start with &lt;strong&gt;z = 0&lt;/strong&gt; and repeatedly apply the function &lt;code&gt;z = z² + c&lt;/code&gt;, the value of &lt;strong&gt;z&lt;/strong&gt; remains bounded (does not go to infinity), no matter how many times you iterate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words, for each point &lt;strong&gt;c&lt;/strong&gt; on the complex plane, you check: if you keep plugging it into this formula, does it stay small, or does it spiral out of control? If it stays small forever, it&amp;rsquo;s in the Mandelbrot set.&lt;/p&gt;
&lt;h2 id="from-simple-formula-to-stunning-images"&gt;From Simple Formula to Stunning Images&lt;a class="anchor ms-1" href="#from-simple-formula-to-stunning-images" aria-label="Permalink: From Simple Formula to Stunning Images"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The real magic happens when you visualize the Mandelbrot set on a computer. Each point in the complex plane is coloured based on whether it belongs to the set or how quickly it escapes to infinity. The result is a black, bulbous shape surrounded by intricate, infinitely detailed patterns that resemble swirling galaxies, seahorses, or lightning bolts—the Mandelbrot set and its &amp;ldquo;fractal boundary.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Zooming in on the edge reveals ever more complexity, with shapes that are reminiscent of the original set at different scales. This property, a form of self-similarity, is a hallmark of fractals.&lt;/p&gt;
&lt;h2 id="why-is-the-mandelbrot-set-important"&gt;Why Is the Mandelbrot Set Important?&lt;a class="anchor ms-1" href="#why-is-the-mandelbrot-set-important" aria-label="Permalink: Why Is the Mandelbrot Set Important?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Mandelbrot set is more than just a pretty picture. It’s a window into the world of &lt;strong&gt;complex dynamics&lt;/strong&gt; and &lt;strong&gt;chaos theory&lt;/strong&gt;. The set’s boundary is infinitely complex, showing how simple mathematical rules can generate unexpected and beautiful complexity.&lt;/p&gt;
&lt;p&gt;It also has deep implications in mathematics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fractals:&lt;/strong&gt; The Mandelbrot set is the most famous example of a fractal—a shape that shows similar, though not identical, patterns at every scale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chaos Theory:&lt;/strong&gt; It illustrates sensitive dependence on initial conditions, where tiny changes can lead to drastically different outcomes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex Numbers:&lt;/strong&gt; It brings to life the beauty of complex numbers, which are usually abstract concepts in math classes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-mandelbrot-set-in-art-and-culture"&gt;The Mandelbrot Set in Art and Culture&lt;a class="anchor ms-1" href="#the-mandelbrot-set-in-art-and-culture" aria-label="Permalink: The Mandelbrot Set in Art and Culture"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thanks to its stunning visuals, the Mandelbrot set has become a cultural icon. It’s inspired digital art, music, and even jewellery. It also serves as a symbol for how complexity and order can emerge from simple rules, a concept that resonates in fields from biology to computer science.&lt;/p&gt;
&lt;h2 id="try-it-yourself"&gt;Try It Yourself&lt;a class="anchor ms-1" href="#try-it-yourself" aria-label="Permalink: Try It Yourself"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Many websites and software tools let you explore the Mandelbrot set interactively. Try zooming in and discover new shapes and patterns emerging at every level. It’s a fantastic way to experience the intersection of math, art, and chaos.&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final Thoughts&lt;a class="anchor ms-1" href="#final-thoughts" aria-label="Permalink: Final Thoughts"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have been fascinated by fractals since I was young. I remember seeing them generated on a &lt;a href="https://blog-dev.funkysi1701.com/posts/2021/back-to-basic/"&gt;Lynx computer&lt;/a&gt;
and the process was slow, one pixel at a time, after a few hours you would see a complete Mandelbrot set. More recently I have looked at generating them in the browser (using .NET&amp;rsquo;s Blazor WebAssembly technology), and the image at the start of this post took only a few seconds to generate.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also created an &lt;a href="https://mandelbrot.funkysi1701.com/" target="_blank" rel="noopener noreferrer"&gt;interactive Mandelbrot set generator&lt;/a&gt;
. It&amp;rsquo;s a work in progress, but you can already generate a set and zoom in using the buttons at the top or by clicking with your mouse. I plan to add more features in the coming weeks.&lt;/p&gt;
&lt;p&gt;The Mandelbrot set reminds us that even the simplest equations can produce infinite complexity and beauty. It stands as an enduring inspiration—proof that mathematics is not just about numbers, but about wonder, creativity, and the endless possibilities hidden within the fabric of reality.&lt;/p&gt;</description></item><item><title>The Hacker Ethic</title><link>https://blog-dev.funkysi1701.com/posts/2025/the-hacker-ethic/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 01 Sep 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/the-hacker-ethic/</guid><category term="hacking">hacking</category><category term="ethics">ethics</category><category term="hackerculture">hackerculture</category><category term="Technology">Technology</category><category term="innovation">innovation</category><category term="curiosity">curiosity</category><category term="Security">Security</category><category term="digitalrights">digitalrights</category><category term="OpenSource">OpenSource</category><category term="mindset">mindset</category><media:content medium="image" type="image/jpeg" url="https://blog-dev.funkysi1701.com/images/hacker.jpg"/><description>&lt;h2 id="the-hacker-ethic-principles-that-shaped-the-digital-age"&gt;The Hacker Ethic: Principles That Shaped the Digital Age&lt;a class="anchor ms-1" href="#the-hacker-ethic-principles-that-shaped-the-digital-age" aria-label="Permalink: The Hacker Ethic: Principles That Shaped the Digital Age"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When most people hear the word “hacker,” they imagine shadowy figures breaking into computer systems. But the original meaning of “hacker” is far richer, rooted in curiosity, creativity, and a set of values that have profoundly shaped technology and culture. This philosophy, known as the &lt;strong&gt;hacker ethic&lt;/strong&gt;, remains a powerful force driving innovation and openness in our digital world.&lt;/p&gt;
&lt;h2 id="what-is-the-hacker-ethic"&gt;What Is the Hacker Ethic?&lt;a class="anchor ms-1" href="#what-is-the-hacker-ethic" aria-label="Permalink: What Is the Hacker Ethic?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The term “hacker ethic” was popularized by Steven Levy in his book &lt;em&gt;Hackers: Heroes of the Computer Revolution&lt;/em&gt; (I have just finished listening to the audio version and highly recommend it). The book describes a set of beliefs and practices developed by early computer enthusiasts at places like MIT, Stanford, and the Homebrew Computer Club. Far from criminality, the hacker ethic is about playful exploration, sharing knowledge, and questioning limits.&lt;/p&gt;
&lt;img src="https://blog-dev.funkysi1701.com/images/hacker.jpg" alt="Hackers: Heroes of the Computer Revolution" width="300" height="400" class="img-fluid float-end ms-2" /&gt;
&lt;h2 id="the-core-principles"&gt;The Core Principles&lt;a class="anchor ms-1" href="#the-core-principles" aria-label="Permalink: The Core Principles"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The hacker ethic can be distilled into several key tenets:&lt;/p&gt;
&lt;h3 id="1-access-to-computers-should-be-unlimited-and-total"&gt;1. Access to Computers Should Be Unlimited and Total&lt;a class="anchor ms-1" href="#1-access-to-computers-should-be-unlimited-and-total" aria-label="Permalink: 1. Access to Computers Should Be Unlimited and Total"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hackers believe that information and tools for learning should be open to all. They value environments where curiosity is encouraged and barriers to entry are low. This spirit led to the creation of open-source software, free educational resources, and collaborative online communities.&lt;/p&gt;
&lt;h3 id="2-all-information-wants-to-be-free"&gt;2. All Information Wants to Be Free&lt;a class="anchor ms-1" href="#2-all-information-wants-to-be-free" aria-label="Permalink: 2. All Information Wants to Be Free"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While not advocating for stealing proprietary data, hackers champion the free flow of information. They believe that sharing code, knowledge, and discoveries accelerates progress and empowers individuals. This principle underlies movements like open data, Creative Commons, and Wikipedia.&lt;/p&gt;
&lt;h3 id="3-mistrust-authoritypromote-decentralization"&gt;3. Mistrust Authority—Promote Decentralization&lt;a class="anchor ms-1" href="#3-mistrust-authoritypromote-decentralization" aria-label="Permalink: 3. Mistrust Authority—Promote Decentralization"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hackers are skeptical of gatekeepers and centralized control. They prefer systems that empower individuals and distribute power. This mindset has influenced everything from peer-to-peer networks to blockchain technology.&lt;/p&gt;
&lt;h3 id="4-hackers-should-be-judged-by-their-hacking-not-bogus-criteria"&gt;4. Hackers Should Be Judged by Their Hacking, Not Bogus Criteria&lt;a class="anchor ms-1" href="#4-hackers-should-be-judged-by-their-hacking-not-bogus-criteria" aria-label="Permalink: 4. Hackers Should Be Judged by Their Hacking, Not Bogus Criteria"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hackers value skill, creativity, and contribution over credentials or status. In hacker culture, what matters is what you build, solve, or improve—not your job title, age, or background. This meritocratic ideal helped shape the egalitarian ethos of the internet.&lt;/p&gt;
&lt;h3 id="5-you-can-create-art-and-beauty-on-a-computer"&gt;5. You Can Create Art and Beauty on a Computer&lt;a class="anchor ms-1" href="#5-you-can-create-art-and-beauty-on-a-computer" aria-label="Permalink: 5. You Can Create Art and Beauty on a Computer"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hackers see computers not only as tools, but as canvases for creativity. From elegant code to digital art, they celebrate the artistry inherent in technology and design.&lt;/p&gt;
&lt;h3 id="6-computers-can-change-your-life-for-the-better"&gt;6. Computers Can Change Your Life for the Better&lt;a class="anchor ms-1" href="#6-computers-can-change-your-life-for-the-better" aria-label="Permalink: 6. Computers Can Change Your Life for the Better"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hackers believe in the transformative potential of technology. They see computers as instruments of empowerment, education, and community—tools that can solve meaningful problems and improve lives.&lt;/p&gt;
&lt;h2 id="why-the-hacker-ethic-matters-today"&gt;Why the Hacker Ethic Matters Today&lt;a class="anchor ms-1" href="#why-the-hacker-ethic-matters-today" aria-label="Permalink: Why the Hacker Ethic Matters Today"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The hacker ethic is alive in open-source communities, maker spaces, and digital activism. It challenges us to question closed systems, embrace sharing, and use technology for good. In a world increasingly shaped by algorithms and artificial intelligence, the hacker ethic reminds us that human creativity, curiosity, and collaboration are still at the heart of progress.&lt;/p&gt;
&lt;p&gt;After listening to the book, it&amp;rsquo;s clear that many of today&amp;rsquo;s security problems stem from this ethic. Early computers and software were built by design without security in mind. The hackers featured in the book strongly disliked passwords and locks; they believed information and systems should be open to all. I had incorrectly assumed this was merely an oversight, or that security wasn&amp;rsquo;t considered necessary at the time.&lt;/p&gt;
&lt;p&gt;The growth of open source software can clearly be seen as an extension to the sharing of code started by these early hackers, instead of looking in a drawer for paper tape containing the code you want to improve, we go to GitHub, but the principle is the same.&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final Thoughts&lt;a class="anchor ms-1" href="#final-thoughts" aria-label="Permalink: Final Thoughts"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The hacker ethic is far more than a technical mindset—it’s a philosophy of openness, empowerment, and playful problem-solving. By understanding and embracing these principles, we can build a digital future that is more inclusive, innovative, and free.&lt;/p&gt;
&lt;p&gt;The more I think about my career with computers, the more I identify as a hacker rather than a programmer or any other job title. I started hacking together simple web pages, I then hacked away at networks and Active Directory, now I hack at .NET and Kubernetes.&lt;/p&gt;
&lt;p&gt;Sponsored Link: &lt;a href="https://amzn.to/49NwGHF" target="_blank" rel="noopener noreferrer"&gt;Hackers: Heroes of the Computer Revolution (book)&lt;/a&gt;
&lt;/p&gt;</description></item><item><title>Kubernetes SSL with Let's Encrypt</title><link>https://blog-dev.funkysi1701.com/posts/2025/kubernetes-and-letsencrypt/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 07 Jul 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/kubernetes-and-letsencrypt/</guid><category term="Kubernetes">Kubernetes</category><category term="LetsEncrypt">LetsEncrypt</category><category term="SSL">SSL</category><category term="cert-manager">cert-manager</category><category term="Helm">Helm</category><category term="Cloudflare">Cloudflare</category><category term="DevOps">DevOps</category><category term="Security">Security</category><category term="Automation">Automation</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/cert-manager.png"/><description>&lt;p&gt;I have blogged before about how cool &lt;a href="https://blog-dev.funkysi1701.com/posts/2018/lets-encrypt-is-awesome/"&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt;
is for getting your web things running under https. However I have just got myself a local kubernetes cluster and it is super easy to spin up new web services with SSL certs.&lt;/p&gt;
&lt;p&gt;The basic instructions can be found &lt;a href="https://www.slingacademy.com/article/how-to-set-up-ssl-with-lets-encrypt-in-kubernetes/" target="_blank" rel="noopener noreferrer"&gt;here&lt;/a&gt;
but let&amp;rsquo;s look at what was involved.&lt;/p&gt;
&lt;p&gt;First of all lets get Cert Manager installed on kubernetes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.1/cert-manager.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the cert manager pods are up, you need to create an issuer which communicates with the lets encrypt API. The first code snippet uses the lets encrypt staging environment to avoid any API limits, the second uses production and uses the cloudflare API to authorize SSL requests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ClusterIssuer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;letsencrypt-staging&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;acme&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://acme-staging-v02.api.letsencrypt.org/directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;email&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;your-email@example.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;privateKeySecretRef&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;letsencrypt-staging&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;solvers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;http01&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ingress&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ingressClassName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;nginx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ClusterIssuer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;letsencrypt-prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;acme&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://acme-v02.api.letsencrypt.org/directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;email&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;your-email@example.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;privateKeySecretRef&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;letsencrypt-prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;solvers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;dns01&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cloudflare&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;apiTokenSecretRef&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;key&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;api-key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cloudflare-api-token-secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;email&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;your-email@example.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now that is all configured all I need to do is update my helm chart and any pod I like can have a sub domain of funkysi1701.com with a lets encrypt SSL cert.&lt;/p&gt;
&lt;p&gt;This is a section from my helm chart which defines the domain name to use and what issuer to use for the certificate.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;ingress&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;className&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;annotations&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cert-manager.io/cluster-issuer&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;letsencrypt-prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;devHost&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;helloworld-dev.funkysi1701.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;testHost&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;helloworld-test.funkysi1701.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tls&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;secretName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;helloworld-dev.funkysi1701.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hosts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;helloworld-dev.funkysi1701.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;secretName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;helloworld-test.funkysi1701.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hosts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;helloworld-test.funkysi1701.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All I need to do now is add similar code like this to every helm chart I publish and my pod will request a SSL certificate. The only manual step I have is to set up a DNS record pointing to the IP address of my cluster for any domain I want to use.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Setting up Let&amp;rsquo;s Encrypt with Kubernetes and Cert Manager has made it incredibly easy to secure my web services with SSL certificates. With just a few YAML configurations and some simple Helm chart updates, I can automatically provision and renew certificates for any subdomain I need. This approach not only saves time but also ensures my services are always protected with up-to-date encryption. If you&amp;rsquo;re running Kubernetes, I highly recommend giving Cert Manager and Let&amp;rsquo;s Encrypt a try for hassle-free SSL management.&lt;/p&gt;</description></item><item><title>Stepping outside my comfort zone</title><link>https://blog-dev.funkysi1701.com/posts/2025/stepping-outside-your-comfort-zone/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 02 Jun 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/stepping-outside-your-comfort-zone/</guid><category term="Adventure">Adventure</category><category term="Family">Family</category><category term="PersonalGrowth">PersonalGrowth</category><category term="Travel">Travel</category><category term="Holiday">Holiday</category><category term="Outdoors">Outdoors</category><category term="Parenting">Parenting</category><category term="Experiences">Experiences</category><media:content medium="image" type="image/jpeg" url="https://blog-dev.funkysi1701.com/images/high-adventure.jpg"/><description>&lt;p&gt;Stepping outside my comfort zone doesn’t come naturally to me. I’m happiest behind my laptop, but every now and then, I know it’s important to try something new. This year, my family and I decided to do just that—and it turned out to be one of our most memorable weeks ever. Curious about what happens when you swap routine for adventure? Read on!&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="anchor ms-1" href="#introduction" aria-label="Permalink: Introduction"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This week was our family holiday. Normally, we’d book a cottage somewhere nice and explore at our own pace. But this often leads to disagreements—everyone wants something different, and sometimes the places we visit aren’t as exciting as we hoped (especially for two kids aged 7 and 9 who get bored easily).&lt;/p&gt;
&lt;p&gt;This time, we chose a different path: a family adventure holiday with &lt;a href="https://www.highadventureholidays.co.uk/" target="_blank" rel="noopener noreferrer"&gt;High Adventure&lt;/a&gt;
. The itinerary was packed: climbing, caving, hiking, water sports, archery, zip wires, and countless other activities. All meals, accommodation, equipment, and expert instructors were included, making it feel both safe and exciting.&lt;/p&gt;
&lt;p&gt;I’ll admit, I was anxious before we set off. I’m not an adventurous person—I like &lt;a href="https://blog-dev.funkysi1701.com/posts/2024/charity-hike"&gt;walking&lt;/a&gt;
, but I’d never tried anything like this. My nine-year-old son was dreading it, while my more adventurous seven-year-old was quietly excited. I worried that if the kids hated it, it would be a waste of money. But as soon as we arrived, those fears melted away.&lt;/p&gt;
&lt;h2 id="saturday"&gt;Saturday&lt;a class="anchor ms-1" href="#saturday" aria-label="Permalink: Saturday"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We arrived on Saturday afternoon and were shown to our room. My only real complaint from the whole week: the room was tiny. With two bunk beds and four of us plus a week’s worth of clothes, it was a challenge to keep things tidy. Thankfully, the rest of the facility was spacious, and our room was right next to the games room—a bonus.&lt;/p&gt;
&lt;p&gt;Dinner was served at 6pm every evening, always a home-cooked meal. While I enjoyed the food, my fussy eaters weren’t always convinced, but there was usually something for everyone. We met the other family we’d be doing activities with.&lt;/p&gt;
&lt;p&gt;After dinner, we explored the Games Room and took on the Labyrinth—a tight, dark maze constructed under the floor. Helmets were required! I found it a bit tight, but my boys absolutely loved it.&lt;/p&gt;
&lt;h2 id="sunday"&gt;Sunday&lt;a class="anchor ms-1" href="#sunday" aria-label="Permalink: Sunday"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;img src="https://blog-dev.funkysi1701.com/images/abseiling.jpg" alt="Abseiling" width="250px" style="padding-right: 15px; float: left; margin-bottom: 0.5rem;" /&gt;
&lt;p&gt;Every day began with a cooked breakfast and a make-your-own sandwich station for lunch. Sunday morning kicked off with climbing and abseiling. I’d never climbed before, so I was surprised—and proud—to make it all the way to the top of the 8-meter wall. My boys didn’t quite reach the top, but they pushed themselves higher than I expected. Abseiling was fun too, and a great way to come down after the climb.&lt;/p&gt;
&lt;p&gt;After lunch, we tried archery. I’d done it once before at my boys’ Scout group, but it took a while to hit the target consistently. Then came bushcraft: foraging for wood, using a knife to make kindling, and lighting a fire with a ferrocerium rod. My nine-year-old and I managed to get a fire going—definitely a memorable moment, especially when we roasted marshmallows.&lt;/p&gt;
&lt;p&gt;The evening’s activity was Nightline: a blindfolded obstacle course. This was a bit much for me, but the boys loved it. My job was to guide them through the course, describing what to climb over, under, or through, and when to stop.&lt;/p&gt;
&lt;h2 id="monday"&gt;Monday&lt;a class="anchor ms-1" href="#monday" aria-label="Permalink: Monday"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Monday started with a walk up the hill to Wainman’s Pinnacle and some “weaseling” through the rocks—a favourite for the boys, especially since they’d done it before with Scouts. I skipped most of the weaseling but enjoyed the walk.&lt;/p&gt;
&lt;p&gt;In the afternoon, it was time for high ropes and a zip wire. We were safely roped in at all times, but stepping off a high ledge still required a leap of faith. My eldest was nervous and had to climb down at first, but after watching me try, he gave it another go—and loved it. That’s what this week was about: encouraging all of us to try new things.&lt;/p&gt;
&lt;p&gt;The evening activity was Eggbert: designing a contraption to protect an egg dropped from a height. We worked as a family, but failed to consider that the weight would flip the egg so it landed the wrong way up. Oops! We didn’t win, but it was great fun seeing everyone’s creative designs.&lt;/p&gt;
&lt;h2 id="tuesday"&gt;Tuesday&lt;a class="anchor ms-1" href="#tuesday" aria-label="Permalink: Tuesday"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;img src="https://blog-dev.funkysi1701.com/images/caves.jpg" alt="Caving" width="250px" style="padding-left: 15px; float: right; margin-bottom: 0.5rem;" /&gt;
&lt;p&gt;Tuesday was our first full day offsite. The instructors drove us in a minibus to the Yorkshire Three Peaks area, where we explored underground caves. Wearing cave suits, wellies, and helmets, we were ready for adventure. I foolishly thought I wouldn’t need a change of clothes—turns out, crawling through water-filled tunnels means your wellies fill up fast! I wasn’t the biggest fan of being soaked, but the boys loved climbing and sliding through the narrow passages. We finished the day with ice cream and hot chocolate in Settle—a well-deserved treat.&lt;/p&gt;
&lt;p&gt;That evening, we tried circus skills and gathered around a campfire. The boys were entertained for ages, trying juggling and balance activities. I didn’t master anything, but I enjoyed relaxing by the fire with my wife and the other parents.&lt;/p&gt;
&lt;h2 id="wednesday"&gt;Wednesday&lt;a class="anchor ms-1" href="#wednesday" aria-label="Permalink: Wednesday"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By Wednesday, tiredness was catching up with me! We tackled a five-mile walk around Malham Cove, taking in stunning views and great weather. The route included Gordale Scar and Janet’s Foss, a lovely waterfall where many people swam or paddled. We hadn’t brought towels, but that didn’t stop my boys from getting soaked. It’s a holiday—why not?&lt;/p&gt;
&lt;p&gt;Wednesday evening was supposed to be a parents’ night off, but we had to cut it short due to a family emergency.&lt;/p&gt;
&lt;h2 id="thursday"&gt;Thursday&lt;a class="anchor ms-1" href="#thursday" aria-label="Permalink: Thursday"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thursday was water sports day, but we decided on a quieter option and visited Haworth instead. The town is famous for the Brontë sisters, and we enjoyed its picturesque streets, a museum, and a play area for the kids—a great way to wind down after so much activity.&lt;/p&gt;
&lt;p&gt;The final evening was a quiz night, complete with questions about the week’s adventures.&lt;/p&gt;
&lt;h2 id="friday"&gt;Friday&lt;a class="anchor ms-1" href="#friday" aria-label="Permalink: Friday"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Friday was going-home day, but not before one last challenge: orienteering. As a former university orienteering club member, I couldn’t resist joining in!&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This week was truly about stepping outside my comfort zone and embracing new experiences. From climbing and caving to bushcraft and zip lines, every activity challenged me and helped me grow. I discovered that pushing your boundaries can be both exciting and rewarding—and creates memories that last a lifetime. If you ever get the chance to try something new, even if it feels a bit daunting, I highly recommend taking the leap. You might just surprise yourself!&lt;/p&gt;</description></item><item><title>Deploy this blog with Helm charts</title><link>https://blog-dev.funkysi1701.com/posts/2025/deploying-hugo-with-helm/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 26 May 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/deploying-hugo-with-helm/</guid><category term="Kubernetes">Kubernetes</category><category term="Helm">Helm</category><category term="DevOps">DevOps</category><category term="Hugo">Hugo</category><category term="Blog">Blog</category><category term="Deployment">Deployment</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/helm-hugo.png"/><description>&lt;p&gt;Helm allows you to package your Kubernetes resources, manage configuration, and deploy updates with a single command.&lt;/p&gt;
&lt;p&gt;In this post, I’ll share how I used Helm to deploy my Hugo blog, making it easy to manage updates and spin up new environments. Full disclosure: My blog currently runs on an Azure Static Web App, but now that I know how to deploy to a Kubernetes cluster, I have the option to switch if needed.&lt;/p&gt;
&lt;h2 id="why-use-helm"&gt;Why Use Helm?&lt;a class="anchor ms-1" href="#why-use-helm" aria-label="Permalink: Why Use Helm?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Helm is the package manager for Kubernetes. It lets you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Template&lt;/strong&gt; your Kubernetes YAML files for reuse and configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version&lt;/strong&gt; your deployments for easy rollbacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share&lt;/strong&gt; your charts with others or use community charts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="creating-a-helm-chart-for-hugo"&gt;Creating a Helm Chart for Hugo&lt;a class="anchor ms-1" href="#creating-a-helm-chart-for-hugo" aria-label="Permalink: Creating a Helm Chart for Hugo"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, I scaffolded a new Helm chart:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm create hugo-blog
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This creates a directory structure with templates for deployments, services, and ingress.&lt;/p&gt;
&lt;h3 id="customizing-the-chart"&gt;Customizing the Chart&lt;a class="anchor ms-1" href="#customizing-the-chart" aria-label="Permalink: Customizing the Chart"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I edited &lt;code&gt;values.yaml&lt;/code&gt; to set my image and service details:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;image&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;repository&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mydockerhubusername/hugo-blog&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Docker image repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tag&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;latest&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Use the latest tag for the image&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;pullPolicy&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;IfNotPresent&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Pull the image only if it’s not already present&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;service&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;NodePort&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Expose the service on a specific port&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Internal port for the service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nodePort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30081&lt;/span&gt; &lt;span style="color:#75715e"&gt;# External port for accessing the service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="deploying-the-blog"&gt;Deploying the Blog&lt;a class="anchor ms-1" href="#deploying-the-blog" aria-label="Permalink: Deploying the Blog"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To deploy the blog, I ran:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install my-hugo-blog ./hugo-blog
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Helm rendered the templates and created the necessary Kubernetes resources.&lt;/p&gt;
&lt;h3 id="updating-the-blog"&gt;Updating the Blog&lt;a class="anchor ms-1" href="#updating-the-blog" aria-label="Permalink: Updating the Blog"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When I rebuild and push a new Docker image, I can upgrade my deployment with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm upgrade my-hugo-blog ./hugo-blog
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This triggers a rolling update in Kubernetes.&lt;/p&gt;
&lt;h3 id="creating-a-docker-image-of-my-blog"&gt;Creating a Docker Image of my Blog&lt;a class="anchor ms-1" href="#creating-a-docker-image-of-my-blog" aria-label="Permalink: Creating a Docker Image of my Blog"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This all assumes that I have a container image of my Hugo blog. Creating this was half the work.&lt;/p&gt;
&lt;p&gt;I had been using a docker-compose file to build my blog; however, this doesn&amp;rsquo;t copy my files into the container it creates a volume to share my files with the container. I needed a Dockerfile that would copy my source code into a container and then save it into a container registry.&lt;/p&gt;
&lt;p&gt;This is the Dockerfile I created. As you can see, it is a two-stage build. First, it takes the Hugo base image, copies my files into it, and builds Hugo. Then the NGINX web server is run, and the default web port 80 is exposed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Step 1: Build the Hugo site&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;floryn90/hugo:0.119.0&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;builder&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Set working directory inside the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/src&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy site files to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; . .&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Build the Hugo site&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; hugo --minify --config config.toml&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Step 2: Serve with nginx&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;nginx:alpine&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Remove the default nginx static files&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; rm -rf /usr/share/nginx/html/*&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the generated site from the builder stage&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;builder /src/public /usr/share/nginx/html&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Expose port 80&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXPOSE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;80&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Start nginx&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-g&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;daemon off;&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="benefits"&gt;Benefits&lt;a class="anchor ms-1" href="#benefits" aria-label="Permalink: Benefits"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repeatable Deployments:&lt;/strong&gt; Easily spin up new environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration Management:&lt;/strong&gt; Override values for different clusters or environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy Updates:&lt;/strong&gt; Upgrade with a single command.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Helm charts have made deploying my Hugo blog to Kubernetes a seamless and efficient process. With features like templating, versioning, and easy updates, Helm is a must-have tool for Kubernetes deployments.&lt;/p&gt;
&lt;p&gt;If you’re managing static sites or microservices, give Helm a try and see how it can simplify your workflow!&lt;/p&gt;
&lt;p&gt;Have you used Helm for static sites or blogs? Share your experience in the comments below!&lt;/p&gt;</description></item><item><title>Learning Kubernetes: A Beginner's Journey</title><link>https://blog-dev.funkysi1701.com/posts/2025/learning-kubernetes/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 12 May 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/learning-kubernetes/</guid><category term="Kubernetes">Kubernetes</category><category term="Docker">Docker</category><category term="Grafana">Grafana</category><category term="ContainerOrchestration">ContainerOrchestration</category><category term="DevOps">DevOps</category><category term="Microservices">Microservices</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/kubernetes.png"/><description>&lt;p&gt;I know nothing (or very little) about Kubernetes. It is high time I do something to learn more about it. Kubernetes is a powerful tool for managing containerized applications, and it&amp;rsquo;s becoming a must-have skill for developers and DevOps engineers. In this blog, I&amp;rsquo;ll share my journey as a beginner exploring Kubernetes, from running a simple Grafana pod to understanding the basics of &lt;code&gt;kubectl&lt;/code&gt;. Let&amp;rsquo;s dive in!&lt;/p&gt;
&lt;h2 id="what-is-kubernetes"&gt;What is Kubernetes?&lt;a class="anchor ms-1" href="#what-is-kubernetes" aria-label="Permalink: What is Kubernetes?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubernetes is an open-source container orchestration platform that automates the deployment, scaling, and management of containerized applications. If you want to run your containerized applications, you probably want to learn Kubernetes.&lt;/p&gt;
&lt;p&gt;I have worked at least two companies so far in my career that have made use of Kubernetes to run their microservice architecture. Let&amp;rsquo;s take a look at how to use it.&lt;/p&gt;
&lt;h3 id="why-learn-kubernetes"&gt;Why Learn Kubernetes?&lt;a class="anchor ms-1" href="#why-learn-kubernetes" aria-label="Permalink: Why Learn Kubernetes?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Kubernetes is widely used in modern software development for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microservices&lt;/strong&gt;: Manage and scale multiple services independently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD Pipelines&lt;/strong&gt;: Automate deployments and rollbacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Handle traffic spikes by scaling pods up or down.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="docker-desktop"&gt;Docker Desktop&lt;a class="anchor ms-1" href="#docker-desktop" aria-label="Permalink: Docker Desktop"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.docker.com/products/docker-desktop/" target="_blank" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;
is a tool for running containers, but it also allows running of Kubernetes.&lt;/p&gt;
&lt;p&gt;I am not going to talk through installing docker desktop and getting a Kubernetes cluster set up, for that check out the &lt;a href="https://docs.docker.com/desktop/use-desktop/kubernetes/" target="_blank" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;
however I found it very straightforward.&lt;/p&gt;
&lt;h2 id="kubectl"&gt;kubectl&lt;a class="anchor ms-1" href="#kubectl" aria-label="Permalink: kubectl"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;kubectl is the command line tool for doing everything with Kubernetes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This prints out basic version information, if this works you know Kubernetes must be working.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl config get-contexts
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl config use-context docker-desktop
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command allows you to switch contexts, to use with docker desktop you want to use the docker-desktop context. At work we have dev and test contexts that can be selected in a similar way.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Context&lt;/strong&gt;: A Kubernetes context is a set of access parameters for a cluster. It allows you to switch between different clusters or environments (e.g., dev, test, production).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get all
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gives you a list of everything that is running in the default namespace, pods, services, deployments etc For a complete list, you can add the &amp;ndash;all-namespaces flag.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Namespace&lt;/strong&gt;: A namespace is a way to organize resources in a Kubernetes cluster, providing isolation between different projects or teams.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hello-world"&gt;Hello World&lt;a class="anchor ms-1" href="#hello-world" aria-label="Permalink: Hello World"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now what is the equivalent of &amp;ldquo;Hello World&amp;rdquo; for Kubernetes? I think getting a container to run in Kubernetes is a pretty good test.&lt;/p&gt;
&lt;p&gt;Open up VS Code and create a yaml file called grafana.yaml&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Specifies the API version for the resource&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Pod&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Defines the resource type as a Pod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana&lt;/span&gt; &lt;span style="color:#75715e"&gt;# The name of the pod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;containers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana&lt;/span&gt; &lt;span style="color:#75715e"&gt;# The name of the container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana/grafana:12.0.0&lt;/span&gt; &lt;span style="color:#75715e"&gt;# The container image to use&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt; &lt;span style="color:#75715e"&gt;# The port exposed by the container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This file uses the Grafana container image, which I am familiar with (see &lt;a href="https://blog-dev.funkysi1701.com/posts/2025/setting-up-grafana/"&gt;my previous blog posts&lt;/a&gt;
) It&amp;rsquo;s default port is 3000 which you can see is defined in this file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply &lt;span style="color:#f92672"&gt;-f&lt;/span&gt; grafana.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This deploys a pod, using the above yaml file.&lt;/p&gt;
&lt;p&gt;You can run the following to find out more about the pod.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl describe pod grafana
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From what I have discovered so far, Kubernetes runs in its own network. So going to localhost:3000 won&amp;rsquo;t work. But you can use port forward to get round this issue.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Port-Forwarding&lt;/strong&gt;: This allows you to access a pod&amp;rsquo;s service on your local machine by forwarding a port from the pod to your localhost.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl port-forward pod/grafana &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now if I try http://localhost:3000/login I see the login page for the Grafana container. A pretty good first test of using Kubernetes.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;Whats next?&lt;a class="anchor ms-1" href="#whats-next" aria-label="Permalink: Whats next?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I want to replace my local deployment of containers that is currently using a docker compose file with Kubernetes. I also want to learn more about &lt;a href="https://helm.sh/" target="_blank" rel="noopener noreferrer"&gt;Helm charts&lt;/a&gt;
as I believe these are an excellent way of packaging up changes to Kubernetes.&lt;/p&gt;
&lt;p&gt;Have you tried running a pod in Kubernetes? What challenges did you face, and how did you overcome them? Share your thoughts in the comments below!&lt;/p&gt;</description></item><item><title>Getting started with OpenTelemetry</title><link>https://blog-dev.funkysi1701.com/posts/2025/getting-started-with-opentelemetry/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 05 May 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/getting-started-with-opentelemetry/</guid><category term="OpenTelemetry">OpenTelemetry</category><category term="Observability">Observability</category><category term="Tracing">Tracing</category><category term="Metrics">Metrics</category><category term="Logging">Logging</category><category term="DotNet">DotNet</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/open-telemetry.png"/><description>&lt;h2 id="what-is-opentelemetry"&gt;What is OpenTelemetry?&lt;a class="anchor ms-1" href="#what-is-opentelemetry" aria-label="Permalink: What is OpenTelemetry?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenTelemetry is an open-source observability framework that provides a unified way to collect, process, and export telemetry data such as &lt;strong&gt;traces&lt;/strong&gt;, &lt;strong&gt;metrics&lt;/strong&gt;, and &lt;strong&gt;logs&lt;/strong&gt;. It is designed to help developers monitor and debug distributed systems by providing insights into application performance and behavior.&lt;/p&gt;
&lt;p&gt;With OpenTelemetry, you can instrument your applications to gain visibility into how requests flow through your system, identify bottlenecks, and troubleshoot issues effectively.&lt;/p&gt;
&lt;h2 id="why-use-opentelemetry"&gt;Why Use OpenTelemetry?&lt;a class="anchor ms-1" href="#why-use-opentelemetry" aria-label="Permalink: Why Use OpenTelemetry?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are some key benefits of using OpenTelemetry:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Unified Observability&lt;/strong&gt;: Collect traces, metrics, and logs using a single framework.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vendor-Neutral&lt;/strong&gt;: Export telemetry data to any backend, such as Azure Monitor, Grafana, or Prometheus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardized APIs&lt;/strong&gt;: Use consistent APIs and SDKs across multiple programming languages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;: Easily extend OpenTelemetry to meet your specific observability needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Community-Driven&lt;/strong&gt;: Backed by a large and active open-source community.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="key-components-of-opentelemetry"&gt;Key Components of OpenTelemetry&lt;a class="anchor ms-1" href="#key-components-of-opentelemetry" aria-label="Permalink: Key Components of OpenTelemetry"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenTelemetry consists of the following core components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Traces&lt;/strong&gt;: Capture the lifecycle of a request as it flows through your system. Traces help you understand dependencies and latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metrics&lt;/strong&gt;: Collect numerical data, such as request counts, CPU usage, and memory consumption, to monitor system health.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logs&lt;/strong&gt;: Record discrete events, such as errors or state changes, to provide additional context for debugging.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="setting-up-opentelemetry-in-net"&gt;Setting Up OpenTelemetry in .NET&lt;a class="anchor ms-1" href="#setting-up-opentelemetry-in-net" aria-label="Permalink: Setting Up OpenTelemetry in .NET"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here’s how you can get started with OpenTelemetry in a .NET application:&lt;/p&gt;
&lt;h3 id="step-1-install-the-required-nuget-packages"&gt;Step 1: Install the Required NuGet Packages&lt;a class="anchor ms-1" href="#step-1-install-the-required-nuget-packages" aria-label="Permalink: Step 1: Install the Required NuGet Packages"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Run the following commands to install the OpenTelemetry libraries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package OpenTelemetry
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package OpenTelemetry.Extensions.Hosting
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package OpenTelemetry.Exporter.Console
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="step-2-configure-opentelemetry-in-your-application"&gt;Step 2: Configure OpenTelemetry in Your Application&lt;a class="anchor ms-1" href="#step-2-configure-opentelemetry-in-your-application" aria-label="Permalink: Step 2: Configure OpenTelemetry in Your Application"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Add the following code to your Program.cs or Startup.cs file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; OpenTelemetry;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; OpenTelemetry.Trace;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; OpenTelemetry.Metrics;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Add OpenTelemetry tracing and metrics&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddOpenTelemetry()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithTracing(tracerProviderBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tracerProviderBuilder
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddAspNetCoreInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddHttpClientInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddConsoleExporter(); &lt;span style="color:#75715e"&gt;// Export traces to the console&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithMetrics(metricsProviderBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; metricsProviderBuilder
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddAspNetCoreInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddRuntimeInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddConsoleExporter(); &lt;span style="color:#75715e"&gt;// Export metrics to the console&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGet(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;, () =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Hello, OpenTelemetry!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="step-3-run-your-application"&gt;Step 3: Run Your Application&lt;a class="anchor ms-1" href="#step-3-run-your-application" aria-label="Permalink: Step 3: Run Your Application"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Start your application and observe the telemetry data in the console. You’ll see traces and metrics being logged as requests are processed.&lt;/p&gt;
&lt;h2 id="aspire-and-how-to-view-telemetry"&gt;Aspire and how to view Telemetry&lt;a class="anchor ms-1" href="#aspire-and-how-to-view-telemetry" aria-label="Permalink: Aspire and how to view Telemetry"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If your .NET code is .Net 8 or higher you can use Aspire which configures OpenTelemetry for free! Check out &lt;a href="https://blog-dev.funkysi1701.com/posts/2024/aspire"&gt;Getting started with .Net Aspire&lt;/a&gt;
. Once Aspire is setup, you will be able to see Logs, Traces and Metrics in the Aspire Dashboard.&lt;/p&gt;
&lt;p&gt;Another popular way to view your data is Grafana, where you can create custom dashboards. I have talked about adding &lt;a href="https://blog-dev.funkysi1701.com/posts/2025/opentelemetry-logs/"&gt;Logs&lt;/a&gt;
to Grafana recently.&lt;/p&gt;
&lt;p&gt;There are lots of other ways to view your data, like Application Insights, DataDog, Jaeger, New Relic and many more.&lt;/p&gt;
&lt;p&gt;But you are not tied to one solution, you can send your data to more than one of these if you wish.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenTelemetry simplifies observability by providing a unified framework for collecting traces, metrics, and logs. Whether you&amp;rsquo;re building microservices, monitoring distributed systems, or debugging performance issues, OpenTelemetry is a powerful tool to have in your arsenal.&lt;/p&gt;
&lt;p&gt;Start using OpenTelemetry today and gain deeper insights into your applications!&lt;/p&gt;
&lt;p&gt;Have you tried OpenTelemetry? Share your experiences and tips in the comments below!&lt;/p&gt;</description></item><item><title>New in .NET Aspire 9.2: diagrams</title><link>https://blog-dev.funkysi1701.com/posts/2025/aspire-9.2/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 21 Apr 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/aspire-9.2/</guid><category term="DotNet">DotNet</category><category term="Aspire">Aspire</category><category term="InfrastructureDiagrams">InfrastructureDiagrams</category><category term="VisualStudio">VisualStudio</category><category term="Docker">Docker</category><category term="Microservices">Microservices</category><category term="Grafana">Grafana</category><category term="Programming">Programming</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/work.PNG"/><description>&lt;p&gt;.NET Aspire 9.2 is here, bringing exciting new features to simplify infrastructure visualization. Whether you&amp;rsquo;re managing microservices or exploring Docker-based architectures, this release makes it easier than ever to create detailed infrastructure diagrams with minimal effort. Let’s dive into the highlights and see how you can use these tools to streamline your workflow.&lt;/p&gt;
&lt;p&gt;For details of what .NET Aspire is, check out my &lt;a href="https://blog-dev.funkysi1701.com/posts/2024/aspire"&gt;post&lt;/a&gt;
from last year. For the official announcement see &lt;a href="https://aspire.dev/whats-new/aspire-13-1/" target="_blank" rel="noopener noreferrer"&gt;What&amp;rsquo;s new in .NET Aspire 9.2&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;My favourite feature from this release is the ability to quickly and easily spin up infrastructure diagrams like this:&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Aspire Diagram" src="https://blog-dev.funkysi1701.com/images/aspire-9.2.jpg" loading="lazy"
width="764" height="675"
/&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;An example infrastructure diagram created using .NET Aspire 9.2.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I also created one last week for my blog about &lt;a href="https://blog-dev.funkysi1701.com/posts/2025/opentelemetry-logs/"&gt;Grafana&lt;/a&gt;
:&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Grafana Example" src="https://blog-dev.funkysi1701.com/images/grafana-loki-arch.png" loading="lazy"
width="811" height="593"
/&gt;
&lt;/p&gt;
&lt;h2 id="so-what-code-is-needed-to-create-one-of-these-diagrams"&gt;So what code is needed to create one of these diagrams?&lt;a class="anchor ms-1" href="#so-what-code-is-needed-to-create-one-of-these-diagrams" aria-label="Permalink: So what code is needed to create one of these diagrams?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open Visual Studio and create a new project of type &amp;ldquo;.NET Aspire App Host&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Edit the Program.cs and add docker containers for anything you want to display on the diagram&lt;/li&gt;
&lt;li&gt;The syntax for this is is shown in my Grafana example:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = DistributedApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Add a .NET application container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; dotnet = builder.AddContainer(&lt;span style="color:#e6db74"&gt;&amp;#34;dotnet-application&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;anyimage&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithHttpEndpoint(port: &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;, targetPort: &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Expose HTTP port 80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Add a Loki container for log aggregation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; loki = builder.AddContainer(&lt;span style="color:#e6db74"&gt;&amp;#34;loki&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;grafana/loki&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;3.4.3&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithHttpEndpoint(port: &lt;span style="color:#ae81ff"&gt;3100&lt;/span&gt;, targetPort: &lt;span style="color:#ae81ff"&gt;3100&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Expose HTTP port 3100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Add an Alloy container for telemetry processing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; alloy = builder.AddContainer(&lt;span style="color:#e6db74"&gt;&amp;#34;alloy&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;grafana/alloy&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;v1.8.1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithHttpEndpoint(port: &lt;span style="color:#ae81ff"&gt;12345&lt;/span&gt;, targetPort: &lt;span style="color:#ae81ff"&gt;12345&lt;/span&gt;, name: &lt;span style="color:#e6db74"&gt;&amp;#34;UI&amp;#34;&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// Expose UI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithHttpEndpoint(port: &lt;span style="color:#ae81ff"&gt;4317&lt;/span&gt;, targetPort: &lt;span style="color:#ae81ff"&gt;4317&lt;/span&gt;, name: &lt;span style="color:#e6db74"&gt;&amp;#34;gRPC&amp;#34;&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// Expose gRPC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithHttpEndpoint(port: &lt;span style="color:#ae81ff"&gt;4318&lt;/span&gt;, targetPort: &lt;span style="color:#ae81ff"&gt;4318&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Expose HTTP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Add a Grafana container for visualization&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; grafana = builder.AddContainer(&lt;span style="color:#e6db74"&gt;&amp;#34;grafana&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;grafana/grafana&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;11.6.0&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithHttpEndpoint(port: &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;, targetPort: &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Expose HTTP port 3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Define relationships between containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet.WithReferenceRelationship(alloy); &lt;span style="color:#75715e"&gt;// Logs flow from .NET app to Alloy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;alloy.WithReferenceRelationship(loki); &lt;span style="color:#75715e"&gt;// Logs flow from Alloy to Loki&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loki.WithReferenceRelationship(grafana); &lt;span style="color:#75715e"&gt;// Logs flow from Loki to Grafana&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Build().Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The parameters for &lt;strong&gt;AddContainer()&lt;/strong&gt; are a name that gets displayed on the diagram, the docker image name, and the tag. The tag is optional and it will use latest if omitted. For my Grafana example, the first docker container I added is for any dotnet application. I can use any docker image for this, as all I want is a container labelled &amp;ldquo;dotnet-application&amp;rdquo;. The &lt;strong&gt;.WithHttpEndpoint&lt;/strong&gt; is for public ports to be displayed so you can see how communication is flowing between containers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;.WithReferenceRelationship()&lt;/strong&gt; is how you define what calls what. So dotnet.WithReferenceRelationship(alloy) adds an arrow pointing from my dotnet application to alloy, suggesting that open telemetry logs flow from my application to alloy. You can also add calls in both directions by adding alloy.WithReferenceRelationship(dotnet) this will change the arrow to point in both directions.&lt;/p&gt;
&lt;p&gt;At my work we use a microservice architecture so I thought it might be fun to try and map out what calls what:&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Work Architecture" src="https://blog-dev.funkysi1701.com/images/work.PNG" loading="lazy"
width="2622" height="1869"
/&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A simplified view of a microservices architecture at work (service names anonymized).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I made a start mapping a few of our services, but this doesn&amp;rsquo;t cover everything and I have replaced the names of the services so no commerical secrets can be identified. I was surprised at how complex the system is.&lt;/p&gt;
&lt;h3 id="real-world-applications"&gt;Real-World Applications&lt;a class="anchor ms-1" href="#real-world-applications" aria-label="Permalink: Real-World Applications"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The ability to generate infrastructure diagrams is invaluable for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microservices Architecture&lt;/strong&gt;: Visualize how services interact and identify bottlenecks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging&lt;/strong&gt;: Quickly understand communication flows to troubleshoot issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Create up-to-date diagrams for team collaboration and onboarding.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;.NET Aspire 9.2 makes it easier than ever to create detailed infrastructure diagrams, saving time and improving collaboration. Whether you&amp;rsquo;re managing microservices or exploring Docker-based architectures, these tools can help you visualize and optimize your systems. Give it a try and see how it can enhance your workflow!&lt;/p&gt;
&lt;p&gt;Have you tried .NET Aspire 9.2? What features are you most excited about? Share your thoughts and experiences in the comments below!&lt;/p&gt;</description></item><item><title>OpenTelemetry logs via Grafana Loki</title><link>https://blog-dev.funkysi1701.com/posts/2025/opentelemetry-logs/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 14 Apr 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/opentelemetry-logs/</guid><category term="OpenTelemetry">OpenTelemetry</category><category term="Grafana">Grafana</category><category term="Loki">Loki</category><category term="Alloy">Alloy</category><category term="Logging">Logging</category><category term="Monitoring">Monitoring</category><category term="Docker">Docker</category><category term="DotNet">DotNet</category><category term="Tech">Tech</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/grafana-loki-arch.png"/><description>&lt;p&gt;Logs are a critical part of monitoring and debugging modern applications. By integrating OpenTelemetry logs with Grafana using Loki and Alloy, you can centralize your log data, making it easier to analyze and monitor your .NET applications. In this blog, I&amp;rsquo;ll walk you through the setup process and share how these tools work together to streamline log aggregation and visualization.&lt;/p&gt;
&lt;p&gt;I have blogged about &lt;a href="https://blog-dev.funkysi1701.com/posts/2025/setting-up-grafana/"&gt;Grafana&lt;/a&gt;
before but until now I haven&amp;rsquo;t added logs to Grafana let&amp;rsquo;s have a look at how that works.&lt;/p&gt;
&lt;p&gt;My dotnet application makes use of OpenTelemetry. I have been sending this telemetry to an Aspire Dashboard running in Docker. This allows me to view logs, metrics and traces.&lt;/p&gt;
&lt;p&gt;Grafana has several tools that can help with logs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Loki&lt;/strong&gt;: A log aggregation system designed to store and query logs from applications and infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alloy&lt;/strong&gt;: An OpenTelemetry collector that processes and forwards telemetry data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry&lt;/strong&gt;: A set of APIs, libraries, and tools for collecting and exporting telemetry data like logs, metrics, and traces.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am using Alloy to collect OpenTelemetry from my application and send it to Loki, The logs in Loki can then be read by Grafana.&lt;/p&gt;
&lt;h2 id="setting-up-loki-and-alloy-with-docker-compose"&gt;Setting Up Loki and Alloy with Docker Compose&lt;a class="anchor ms-1" href="#setting-up-loki-and-alloy-with-docker-compose" aria-label="Permalink: Setting Up Loki and Alloy with Docker Compose"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To start off let&amp;rsquo;s add Alloy and Loki to my docker compose file.&lt;/p&gt;
&lt;h3 id="docker-compose-configuration"&gt;Docker Compose Configuration&lt;a class="anchor ms-1" href="#docker-compose-configuration" aria-label="Permalink: Docker Compose Configuration"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;docker-compose.yml&lt;/code&gt; file defines the services for Loki and Alloy. Here&amp;rsquo;s what each section does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Loki&lt;/strong&gt;: Runs the Loki container on port 3100 and uses a local configuration file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alloy&lt;/strong&gt;: Runs the Alloy container, exposing ports for receiving telemetry data and processing logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-docker-compose.yml" data-lang="docker-compose.yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Docker Compose configuration for Loki and Alloy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: -&lt;span style="color:#ae81ff"&gt;config.file=/etc/loki/local-config.yaml&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Specifies the configuration file for Loki&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;alloy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana/alloy:v1.8.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;monitoring_alloy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;12345&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12345&lt;/span&gt; &lt;span style="color:#75715e"&gt;# HTTP server for Alloy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;4317&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;4317&lt;/span&gt; &lt;span style="color:#75715e"&gt;# gRPC endpoint for OpenTelemetry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;4319&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;4318&lt;/span&gt; &lt;span style="color:#75715e"&gt;# HTTP endpoint for OpenTelemetry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./config.alloy:/etc/alloy/config.alloy&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Mount Alloy configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;loki&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here is the config file for Alloy.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-alloy.config" data-lang="alloy.config"&gt;otelcol.receiver.otlp &amp;#34;default&amp;#34; {
http {}
grpc {}
output {
logs = [otelcol.processor.batch.default.input]
}
}
otelcol.processor.batch &amp;#34;default&amp;#34; {
output {
logs = [
otelcol.exporter.otlphttp.loki.input,
]
}
}
otelcol.exporter.otlphttp &amp;#34;loki&amp;#34; {
client {
endpoint = &amp;#34;http://loki:3100/otlp&amp;#34;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="how-the-components-work-together"&gt;How the Components Work Together&lt;a class="anchor ms-1" href="#how-the-components-work-together" aria-label="Permalink: How the Components Work Together"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Application Logs&lt;/strong&gt;: The .NET application generates logs and sends them to Alloy on port 4317.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alloy Processing&lt;/strong&gt;: Alloy processes the logs and forwards them to Loki on port 3100.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loki Storage&lt;/strong&gt;: Loki stores the logs, making them available for querying.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grafana Visualization&lt;/strong&gt;: Grafana connects to Loki as a data source, allowing you to visualize and analyze the logs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Architecture Diagram" src="https://blog-dev.funkysi1701.com/images/grafana-loki-arch.png" loading="lazy"
width="811" height="593"
/&gt;
&lt;/p&gt;
&lt;h3 id="handling-aspire-dashboard-logs"&gt;Handling Aspire Dashboard Logs&lt;a class="anchor ms-1" href="#handling-aspire-dashboard-logs" aria-label="Permalink: Handling Aspire Dashboard Logs"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By default, sending logs to Alloy and Loki means Aspire Dashboard will no longer receive them. To resolve this, you can configure the &lt;code&gt;AddOpenTelemetryExporters&lt;/code&gt; method in your .NET application to send logs to multiple destinations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Extensions.cs" data-lang="Extensions.cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (useOtlpExporter)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// The following lines enable the OTLP exporter (requires the OpenTelemetry.Exporter.OpenTelemetryProtocol package)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.Services.AddOpenTelemetry()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithLogging(logging =&amp;gt; logging.AddOtlpExporter(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.Endpoint = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(builder.Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;OTEL_EXPORTER_OTLP_ENDPOINT&amp;#34;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithLogging(logging =&amp;gt; logging.AddOtlpExporter(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.Endpoint = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(builder.Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;GrafanaExporter&amp;#34;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithMetrics(metrics =&amp;gt; metrics.AddOtlpExporter(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.Endpoint = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(builder.Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;OTEL_EXPORTER_OTLP_ENDPOINT&amp;#34;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithTracing(tracing =&amp;gt; tracing.AddOtlpExporter(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.Endpoint = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(builder.Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;OTEL_EXPORTER_OTLP_ENDPOINT&amp;#34;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If I update the AddOpenTelemetryExporters method in my Extensions.cs file in my .Net Aspire ServiceDefaults project, I can get my logs (or traces/metrics) to be sent to as many different places as I want. In my case I am sending to Allot and Aspire Dashboard.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Integrating OpenTelemetry logs with Grafana using Loki and Alloy provides a powerful solution for centralized log aggregation and monitoring. This setup simplifies debugging and enhances visibility into your application&amp;rsquo;s behaviour. If you haven&amp;rsquo;t tried it yet, give it a go and let me know your thoughts in the comments!&lt;/p&gt;</description></item><item><title>Exploring ChatGPT image generation</title><link>https://blog-dev.funkysi1701.com/posts/2025/fun-with-ai/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 07 Apr 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/fun-with-ai/</guid><category term="AI">AI</category><category term="ChatGPT">ChatGPT</category><category term="ImageGeneration">ImageGeneration</category><category term="FunwithAI">FunwithAI</category><category term="Technology">Technology</category><category term="Creativity">Creativity</category><category term="MachineLearning">MachineLearning</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/ai-version-of-me.png"/><description>&lt;h2 id="fun-with-ai"&gt;Fun with AI&lt;a class="anchor ms-1" href="#fun-with-ai" aria-label="Permalink: Fun with AI"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AI tools like &lt;a href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt;
are evolving rapidly, and their creative capabilities are nothing short of amazing. Recently, I decided to test its image generation features with a fun and quirky prompt. The results? A personalized action figure concept that blew my mind! Let me walk you through the process and share the results.&lt;/p&gt;
&lt;p&gt;I used the following prompt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Draw an action figure toy of the person in this photo.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;The action figure should be full-figure and displayed in its original blister pack packaging.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;On top of the box is the name of the toy &amp;#34;Software Developer&amp;#34; across a single line of text.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;In the blister pack packaging, next to the figure, show the toy&amp;#39;s accessories,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;including a Microsoft laptop,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cup of coffee,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;the Visual Studio app,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GitHub app,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;and the Azure app.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;There should be a label on the corner of the box that says &amp;#34;Limited Edition&amp;#34;.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;This figure should look like a premium toy and everything should be inside the plastic!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table border="0" cellpadding="0" cellspacing="0" &gt;
&lt;tr style="border-width:0px 0px 0px 0px"&gt;
&lt;td style="border-width:0px 0px 0px 0px"&gt;
and I supplied the following image:
&lt;/td&gt;
&lt;td style="border-width:0px 0px 0px 0px"&gt;
and this is the result:
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style="border-width:0px 0px 0px 0px"&gt;
&lt;td style="border-width:0px 0px 0px 0px"&gt;
&lt;img src="https://blog-dev.funkysi1701.com/images/ndc-simon.jpg" alt="A picture of me as the source image" width="300" height="400" class="img-fluid" /&gt;
&lt;/td&gt;
&lt;td style="border-width:0px 0px 0px 0px"&gt;
&lt;img src="https://blog-dev.funkysi1701.com/images/ai-version-of-me.png" alt="AI generated version of me" width="300" height="400" class="img-fluid" /&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Pretty cool, right? Well I can&amp;rsquo;t take the credit for the prompt, It comes from &lt;a href="https://www.linkedin.com/posts/amyru_out-of-all-the-chatgpt-image-generation-capabilities-activity-7312549043610521601-BPqJ?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAAAOBLj4B-Ls737kwBrHA1oRvkACb_CoRCQw2X0v1gk7q4r3w8Q9x5a6b0h4j2c1nqZz7g&amp;amp;lipi=urn%3Ali%3Apage%3Ad_flagship3_profile_view_base_post_primary%3B5JdYtG6mQeKx%2FfWl8V9jNQ%3D%3D" target="_blank" rel="noopener noreferrer"&gt;Amy Ru&lt;/a&gt;
on LinkedIn.&lt;/p&gt;
&lt;p&gt;Have you tried ChatGPT&amp;rsquo;s image generation features? What creative prompts have you used? Share your experiences in the comments below!&lt;/p&gt;</description></item><item><title>Automate social posts with AI in .NET</title><link>https://blog-dev.funkysi1701.com/posts/2025/using-ai/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 17 Mar 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/using-ai/</guid><category term="AI">AI</category><category term="OpenAI">OpenAI</category><category term="SemanticKernel">SemanticKernel</category><category term="DotNet">DotNet</category><category term="RSS">RSS</category><category term="SocialMedia">SocialMedia</category><category term="Automation">Automation</category><category term="Programming">Programming</category><category term="Tech">Tech</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/social-ai.png"/><description>&lt;p&gt;Automating repetitive tasks is one of the best ways to save time and focus on what matters most. In this blog, I&amp;rsquo;ll show you how I used AI with .NET to automate the creation of social media posts for my blog. By leveraging my blog&amp;rsquo;s RSS feed, OpenAI, and Semantic Kernel, I was able to generate engaging posts effortlessly. Let&amp;rsquo;s dive in!&lt;/p&gt;
&lt;h2 id="using-openai-for-social-media-posts"&gt;Using OpenAI for Social Media Posts&lt;a class="anchor ms-1" href="#using-openai-for-social-media-posts" aria-label="Permalink: Using OpenAI for Social Media Posts"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s look at a way to use AI for a practical use. For the last few weeks I have been using AI to help me write social media posts to promote my latest blog posts, let&amp;rsquo;s look at how I might automate this using .NET code.&lt;/p&gt;
&lt;p&gt;My blog publishes new posts to a rss feed, I can consume this from .NET code and pass it to AI, and create social media posts from them.&lt;/p&gt;
&lt;p&gt;My blog is written in Hugo and it is already generating an rss feed, however I have made a slight tweak to it. I want the tags from the blog article to be used as hashtags in any social media posts. I am storing the tags in the rss field category.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;category&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;term=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;tagname&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;tagname&lt;span style="color:#f92672"&gt;&amp;lt;/category&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here is the section of code that generates my rss feed, the key tags I will be using are title, link, category and description.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ .Title }}&lt;span style="color:#f92672"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;link&amp;gt;&lt;/span&gt;{{ .Permalink }}&lt;span style="color:#f92672"&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;author&amp;gt;&lt;/span&gt;{{ with .Params.author }}{{ . }}{{ else }}{{ with $.Site.Author.name }}{{.}}{{end}}{{end}}&lt;span style="color:#f92672"&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;pubDate&amp;gt;&lt;/span&gt;{{ .Date.Format &amp;#34;Mon, 02 Jan 2006 15:04:05 -0700&amp;#34; | safeHTML }}&lt;span style="color:#f92672"&gt;&amp;lt;/pubDate&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ with .Site.Author.email }}&lt;span style="color:#f92672"&gt;&amp;lt;author&amp;gt;&lt;/span&gt;{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}&lt;span style="color:#f92672"&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;{{end}}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;guid&amp;gt;&lt;/span&gt;{{ .Permalink }}&lt;span style="color:#f92672"&gt;&amp;lt;/guid&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{- range .Params.tags -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;category&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;term=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;{{ replace . &amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;}}&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;{{ replace . &amp;#34; &amp;#34; &amp;#34;&amp;#34; }}&lt;span style="color:#f92672"&gt;&amp;lt;/category&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;{{ .Content | html }}&lt;span style="color:#f92672"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I am using the nuget package &lt;a href="https://github.com/arminreiter/FeedReader/" target="_blank" rel="noopener noreferrer"&gt;CodeHollow.FeedReader&lt;/a&gt;
, this allows me to pass in a URL to my rss feed, and get an object out which contains all the items in the feed. Using a nuget package saves me doing a lot of parsing of XML. The method I am using takes an input string something like &lt;a href="https://www.funkysi1701.com/index.xml" target="_blank" rel="noopener noreferrer"&gt;https://www.funkysi1701.com/index.xml&lt;/a&gt;
, and gives me an object I can query and do things with.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; RSSFeed = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; FeedReader.ReadAsync(FeedStringValue);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;RSS feed contains the following properties I want to use: Title, Link, Categories and Description. All are strings, except Categories which is an ICollection&amp;lt;string&amp;gt; This matches what I said I wanted to use from the rss feed.&lt;/p&gt;
&lt;p&gt;I want to pass the description to AI, and then combine the Response from AI with URL of the blog post and a comma separated list of &amp;lsquo;hashtags&amp;rsquo; (the categories in my rss feed).&lt;/p&gt;
&lt;p&gt;Initially I thought about using &lt;a href="https://ollama.com/" target="_blank" rel="noopener noreferrer"&gt;ollama&lt;/a&gt;
, this is an example of a LLM (Large Language Model) that you can install on your laptop, sometimes called a Small Language Model (SLM). However, I found this to be unreliable with my laptop and caused several system wide crashes. I assume this is due to memory usage and the size of the model. LLMs or SLMs do require a lot of system resources so this is not completely surprising. I will revisit this at some point as I would like to learn more about ollama.&lt;/p&gt;
&lt;p&gt;I already have an account on &lt;a href="https://platform.openai.com/" target="_blank" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;
and some credit so let&amp;rsquo;s try that instead. I have made a fair few requests using OpenAI and so far it has cost me 11p, I don&amp;rsquo;t think for my usage it is going to break the bank. If I was going to put something on the public internet I may have to reconsider but for my own personal use I think this should be fine.&lt;/p&gt;
&lt;p&gt;Next step, how do I talk to OpenAI from .NET. I know of two approaches, &lt;a href="https://github.com/microsoft/semantic-kernel" target="_blank" rel="noopener noreferrer"&gt;Semantic Kernel&lt;/a&gt;
and the new Microsoft.Extensions.AI libraries introduces in .NET 9 &lt;a href="https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/" target="_blank" rel="noopener noreferrer"&gt;https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/&lt;/a&gt;
. Semantic Kernel is what I am using here, however I do want to explore the newer libraries at some point.&lt;/p&gt;
&lt;p&gt;Semantic Kernel with OpenAI needs the following Nuget package: Microsoft.SemanticKernel.Connectors.OpenAI&lt;/p&gt;
&lt;p&gt;In my Program.cs I had the following to register and add to dependency injection.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (builder.Configuration.GetValue&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;OpenAI:Key&amp;#34;&lt;/span&gt;) != &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.Services.AddOpenAIChatCompletion(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; modelId: builder.Configuration.GetValue&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;OpenAI:Model&amp;#34;&lt;/span&gt;) ?? &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apiKey: builder.Configuration.GetValue&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;OpenAI:Key&amp;#34;&lt;/span&gt;) ?? &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient((serviceProvider) =&amp;gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Kernel(serviceProvider));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;OpenAI:Key is your API Key, which can be found from the OpenAI site, &lt;a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer"&gt;https://platform.openai.com/api-keys&lt;/a&gt;
OpenAI:Model is the model I am using, in my case &amp;ldquo;gpt-4o&amp;rdquo;, I&amp;rsquo;ve added it as a config option as I may find I want to change it.&lt;/p&gt;
&lt;p&gt;Now in the code where I want to call OpenAI I can just inject SemanticKernel and call its async methods. I am a backend developer, so I created a simple API that allows me to call it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AIHandler&lt;/span&gt;(Kernel kernel)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Handle(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; request, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; kernel.InvokePromptAsync(request, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Do something with the response...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the only step I am missing is what string or prompt do I want to pass to OpenAI?&lt;/p&gt;
&lt;p&gt;I want a social media post that will fit into a facebook/twitter/bluesky etc post, some of these services are character limited so I have included a character limit in my &amp;ldquo;prompt&amp;rdquo;, however I am being clever and counting the number of characters taken up by the URL and hashtags and subtracting that first.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; socialMediaPrompt = &lt;span style="color:#e6db74"&gt;$&amp;#34;write a social media post under {240 - length} characters, excluding hash tags about {feedItem.Description}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now once I put is all together I have a page that loads my rss feed, and I can click a button to generate a social media post.&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Ai Example" src="https://blog-dev.funkysi1701.com/images/ai-example.png" loading="lazy"
width="1843" height="432"
/&gt;
&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By combining .NET, Semantic Kernel, and OpenAI, I was able to automate the creation of social media posts, saving time and effort. This approach can be adapted for other use cases, such as summarizing content or generating marketing copy.&lt;/p&gt;
&lt;p&gt;If you have enjoyed this article and want to get a monthly email with all my latest articles, please sign up to my &lt;a href="https://blog-dev.funkysi1701.com/newsletter"&gt;newsletter&lt;/a&gt;
. If you have any questions or want to share your own experiences, feel free to leave a comment below!&lt;/p&gt;</description></item><item><title>Merge two Git projects, keep history</title><link>https://blog-dev.funkysi1701.com/posts/2025/merge-two-projects-into-one/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 10 Mar 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/merge-two-projects-into-one/</guid><category term="Git">Git</category><category term="VersionControl">VersionControl</category><category term="RepositoryManagement">RepositoryManagement</category><category term="Development">Development</category><category term="Tech">Tech</category><category term="Programming">Programming</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/git-merge.png"/><description>&lt;p&gt;I needed to merge two separate projects into one repository while keeping the full commit history from both sides. Copy-paste would have worked for the files, but history would have stayed trapped in the old remotes.&lt;/p&gt;
&lt;p&gt;This approach adds the second repo as a remote, fetches it, and merges with &lt;code&gt;--allow-unrelated-histories&lt;/code&gt; so Git keeps both lineages. I still refer to the repos as &lt;code&gt;project1&lt;/code&gt; and &lt;code&gt;project2&lt;/code&gt;, with remotes at &lt;code&gt;project1Url&lt;/code&gt; and &lt;code&gt;project2Url&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="step-1-clone-the-original-repository"&gt;Step 1: Clone the original repository&lt;a class="anchor ms-1" href="#step-1-clone-the-original-repository" aria-label="Permalink: Step 1: Clone the original repository"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, clone the repository of &lt;code&gt;project1&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone project1Url
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-2-add-the-new-repository-as-a-remote"&gt;Step 2: Add the new repository as a remote&lt;a class="anchor ms-1" href="#step-2-add-the-new-repository-as-a-remote" aria-label="Permalink: Step 2: Add the new repository as a remote"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next, add the repository of &lt;code&gt;project2&lt;/code&gt; as a remote:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git remote add project2 project2Url
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-3-fetch-the-commits-from-the-new-repository"&gt;Step 3: Fetch the commits from the new repository&lt;a class="anchor ms-1" href="#step-3-fetch-the-commits-from-the-new-repository" aria-label="Permalink: Step 3: Fetch the commits from the new repository"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Fetch the commits from the &lt;code&gt;project2&lt;/code&gt; repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git fetch project2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-4-merge-the-commits-from-the-new-repository"&gt;Step 4: Merge the commits from the new repository&lt;a class="anchor ms-1" href="#step-4-merge-the-commits-from-the-new-repository" aria-label="Permalink: Step 4: Merge the commits from the new repository"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Merge the commits from the &lt;code&gt;project2&lt;/code&gt; repository into &lt;code&gt;project1&lt;/code&gt;. Point at the branch that holds the code you want (often &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git merge project2/develop --allow-unrelated-histories
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-5-fix-merge-conflicts"&gt;Step 5: Fix merge conflicts&lt;a class="anchor ms-1" href="#step-5-fix-merge-conflicts" aria-label="Permalink: Step 5: Fix merge conflicts"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If there are conflicts, you will need to resolve them. In my case, this was mainly build files and scripts in the root of the repository. Most of the important code was in subfolders, so this was minimal.&lt;/p&gt;
&lt;h2 id="step-6-push-the-changes-to-the-original-repository"&gt;Step 6: Push the changes to the original repository&lt;a class="anchor ms-1" href="#step-6-push-the-changes-to-the-original-repository" aria-label="Permalink: Step 6: Push the changes to the original repository"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Push the changes to a new branch for review before merging into the main branch:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git checkout -b feature/merge-project2-into-project1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git push origin feature/merge-project2-into-project1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-7-clean-up"&gt;Step 7: Clean up&lt;a class="anchor ms-1" href="#step-7-clean-up" aria-label="Permalink: Step 7: Clean up"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Test your code to ensure everything builds and runs as expected now that it is all in one repository. Create a pull request and review all the changes to ensure nothing gets changed that shouldn&amp;rsquo;t be.&lt;/p&gt;
&lt;p&gt;When you look at your PR, it will look like a lot of code has been added. However, if you look closely at the Git history of those files, you will see that the history has been preserved from the original repository. You can remove the temporary &lt;code&gt;project2&lt;/code&gt; remote once you no longer need it (&lt;code&gt;git remote remove project2&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is still one of the simplest ways to combine two codebases without throwing away history. Use it when consolidating services or folding a spike repo into the main project; open a PR so reviewers can see the merge conflicts and folder layout before it lands on the default branch.&lt;/p&gt;
&lt;h3 id="related-on-this-blog"&gt;Related on this blog&lt;a class="anchor ms-1" href="#related-on-this-blog" aria-label="Permalink: Related on this blog"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once the histories live in one repo, automated promotion helps: see &lt;a href="https://blog-dev.funkysi1701.com/posts/2024/automatic-pull-requests/"&gt;automatic pull requests&lt;/a&gt;
for GitHub Actions and Azure DevOps patterns, and &lt;a href="https://blog-dev.funkysi1701.com/posts/2022/using-github-actions/"&gt;using GitHub Actions&lt;/a&gt;
if you are wiring CI around the merge.&lt;/p&gt;
&lt;p&gt;If you have enjoyed this article and want to get a monthly email with all my latest articles, please sign up for my &lt;a href="https://blog-dev.funkysi1701.com/newsletter"&gt;newsletter&lt;/a&gt;
. If you have any questions or comments, please feel free to reach out or leave a comment below.&lt;/p&gt;</description></item><item><title>The Pragmatic Programmer</title><link>https://blog-dev.funkysi1701.com/posts/2025/pragmatic-programmer/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 24 Feb 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/pragmatic-programmer/</guid><category term="PragmaticProgrammer">PragmaticProgrammer</category><category term="BookReview">BookReview</category><category term="Programming">Programming</category><category term="SoftwareDevelopment">SoftwareDevelopment</category><category term="BestPractices">BestPractices</category><category term="Tech">Tech</category><media:content medium="image" type="image/jpeg" url="https://blog-dev.funkysi1701.com/images/tpp.jpg"/><description>&lt;p&gt;I have been reading &lt;a href="https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/" target="_blank" rel="noopener noreferrer"&gt;The Pragmatic Programmer&lt;/a&gt;
and I have to say it is a fantastic book. It is full of great advice and tips for programmers. I highly recommend it to anyone who is a programmer at any level.&lt;/p&gt;
&lt;p&gt;Lots of the advice is common sense and things I already do, but there are also lots of things I know in the back of my mind that I should be doing. So reading it has been a great reminder of those things.&lt;/p&gt;
&lt;p&gt;Here is the complete list of tips that can be found in the book (I am reading the 20th anniversary edition, so these might be slightly different if you are looking at the original edition):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Care About Your Craft&lt;/li&gt;
&lt;li&gt;Think! About Your Work&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="a-pragmatic-philosophy"&gt;A Pragmatic Philosophy&lt;a class="anchor ms-1" href="#a-pragmatic-philosophy" aria-label="Permalink: A Pragmatic Philosophy"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="3"&gt;
&lt;li&gt;You Have Agency&lt;/li&gt;
&lt;li&gt;Provide Options, Don’t Make Lame Excuses&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog-dev.funkysi1701.com/posts/2015/pragmatic-programmer-broken-windows/"&gt;Don’t Live with Broken Windows&lt;/a&gt;
(I blogged about this in 2015)&lt;/li&gt;
&lt;li&gt;Be a Catalyst for Change&lt;/li&gt;
&lt;li&gt;Remember the Big Picture&lt;/li&gt;
&lt;li&gt;Make Quality a Requirements Issue&lt;/li&gt;
&lt;li&gt;Invest Regularly in Your Knowledge Portfolio&lt;/li&gt;
&lt;li&gt;Critically Analyze What You Read and Hear&lt;/li&gt;
&lt;li&gt;English is Just Another Programming Language&lt;/li&gt;
&lt;li&gt;It’s Both What You Say and the Way You Say It&lt;/li&gt;
&lt;li&gt;Build Documentation In, Don’t Bolt It On&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="a-pragmatic-approach"&gt;A Pragmatic Approach&lt;a class="anchor ms-1" href="#a-pragmatic-approach" aria-label="Permalink: A Pragmatic Approach"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="14"&gt;
&lt;li&gt;Good Design Is Easier to Change Than Bad Design&lt;/li&gt;
&lt;li&gt;DRY - Don’t Repeat Yourself&lt;/li&gt;
&lt;li&gt;Make It Easy to Reuse&lt;/li&gt;
&lt;li&gt;Eliminate Effects Between Unrelated Things&lt;/li&gt;
&lt;li&gt;There Are No Final Decisions&lt;/li&gt;
&lt;li&gt;Forgo Following Fads&lt;/li&gt;
&lt;li&gt;Use Tracer Bullets to Find the Target&lt;/li&gt;
&lt;li&gt;Prototype to Learn&lt;/li&gt;
&lt;li&gt;Program Close to the Problem Domain&lt;/li&gt;
&lt;li&gt;Estimate to Avoid Surprises&lt;/li&gt;
&lt;li&gt;Iterate the Schedule with the Code&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-basic-tools"&gt;The Basic Tools&lt;a class="anchor ms-1" href="#the-basic-tools" aria-label="Permalink: The Basic Tools"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="25"&gt;
&lt;li&gt;Keep Knowledge in Plain Text&lt;/li&gt;
&lt;li&gt;Use the Power of Command Shells&lt;/li&gt;
&lt;li&gt;Achieve Editor Fluency&lt;/li&gt;
&lt;li&gt;Always Use Version Control&lt;/li&gt;
&lt;li&gt;Fix the Problem, Not the Blame&lt;/li&gt;
&lt;li&gt;Don’t Panic&lt;/li&gt;
&lt;li&gt;Failing Test Before Fixing Code&lt;/li&gt;
&lt;li&gt;Read the Damn Error Message&lt;/li&gt;
&lt;li&gt;&amp;ldquo;select&amp;rdquo; Isn&amp;rsquo;t Broken&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t Assume It - Prove it&lt;/li&gt;
&lt;li&gt;Learn a Text Manipulation Language&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="pragmatic-paranoia"&gt;Pragmatic Paranoia&lt;a class="anchor ms-1" href="#pragmatic-paranoia" aria-label="Permalink: Pragmatic Paranoia"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="36"&gt;
&lt;li&gt;You Can&amp;rsquo;t Write Perfect Software&lt;/li&gt;
&lt;li&gt;Design with Contracts&lt;/li&gt;
&lt;li&gt;Crash Early&lt;/li&gt;
&lt;li&gt;Use Assertions to Prevent the Impossible&lt;/li&gt;
&lt;li&gt;Finish What You Start&lt;/li&gt;
&lt;li&gt;Act Locally&lt;/li&gt;
&lt;li&gt;Take Small Steps - Always&lt;/li&gt;
&lt;li&gt;Avoid Fortune Telling&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="bend-or-break"&gt;Bend, or Break&lt;a class="anchor ms-1" href="#bend-or-break" aria-label="Permalink: Bend, or Break"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="44"&gt;
&lt;li&gt;Decoupled Code is Easy to Change&lt;/li&gt;
&lt;li&gt;Tell, Don&amp;rsquo;t Ask&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t Chain Method Calls&lt;/li&gt;
&lt;li&gt;Avoid Global Data&lt;/li&gt;
&lt;li&gt;If it&amp;rsquo;s Important Enough to be Global, Wrap It in an API&lt;/li&gt;
&lt;li&gt;Programming is About Code, But Programs Are About Data&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t Hoard State; Pass It Around&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t Pay Inheritance Tax&lt;/li&gt;
&lt;li&gt;Prefer Interfaces to Express Polymorphism&lt;/li&gt;
&lt;li&gt;Delegate to Services: Has-A Trumps Is-A&lt;/li&gt;
&lt;li&gt;Use Mixins to Share Functionality&lt;/li&gt;
&lt;li&gt;Parameterize Your App Using External Configuration&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="concurrency"&gt;Concurrency&lt;a class="anchor ms-1" href="#concurrency" aria-label="Permalink: Concurrency"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="56"&gt;
&lt;li&gt;Analyze Workflow to Improve Concurrency&lt;/li&gt;
&lt;li&gt;Shared State is Incorrect State&lt;/li&gt;
&lt;li&gt;Random Failures Are Often Concurrency Issues&lt;/li&gt;
&lt;li&gt;Use Actors for Concurrency Without Shared State&lt;/li&gt;
&lt;li&gt;Use Blackboards to Coordinate Workflow&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="while-you-are-coding"&gt;While You Are Coding&lt;a class="anchor ms-1" href="#while-you-are-coding" aria-label="Permalink: While You Are Coding"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="61"&gt;
&lt;li&gt;Listen to Your Inner Lizard&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t Program by Coincidence&lt;/li&gt;
&lt;li&gt;Estimate the Order of Your Algorithms&lt;/li&gt;
&lt;li&gt;Test Your Estimates&lt;/li&gt;
&lt;li&gt;Refactor Early, Refactor Often&lt;/li&gt;
&lt;li&gt;Testing is Not About Finding Bugs&lt;/li&gt;
&lt;li&gt;A Test is the First User of Your Code&lt;/li&gt;
&lt;li&gt;Build End-to-End, Not Top-Down or Bottom-Up&lt;/li&gt;
&lt;li&gt;Design To Test&lt;/li&gt;
&lt;li&gt;Test Your Software, or Your Users Will&lt;/li&gt;
&lt;li&gt;Use Property-Based Tests to Validate your Assumptions&lt;/li&gt;
&lt;li&gt;Keep it Simple and Minimize Attack Surfaces&lt;/li&gt;
&lt;li&gt;Apply Security Patches Quickly&lt;/li&gt;
&lt;li&gt;Name well; Rename When Needed&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="before-the-project"&gt;Before the Project&lt;a class="anchor ms-1" href="#before-the-project" aria-label="Permalink: Before the Project"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="75"&gt;
&lt;li&gt;No One Knows Exactly What They Want&lt;/li&gt;
&lt;li&gt;Programmers Help People Understand What They Want&lt;/li&gt;
&lt;li&gt;Requirements are Learned in a Feedback Loop&lt;/li&gt;
&lt;li&gt;Work with a User to Think Like a User&lt;/li&gt;
&lt;li&gt;Policy is Metadata&lt;/li&gt;
&lt;li&gt;Use a Project Glossary&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t Think Outside the Box - Find the Box&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t Go into the Code Alone&lt;/li&gt;
&lt;li&gt;Agile is Not a Noun; Agile is How You Do Things&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="pragmatic-projects"&gt;Pragmatic Projects&lt;a class="anchor ms-1" href="#pragmatic-projects" aria-label="Permalink: Pragmatic Projects"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="84"&gt;
&lt;li&gt;Maintain Small, Stable Teams&lt;/li&gt;
&lt;li&gt;Schedule It to Make it Happen&lt;/li&gt;
&lt;li&gt;Organize Fully Functional Teams&lt;/li&gt;
&lt;li&gt;Do What Works, Not What&amp;rsquo;s Fashionable&lt;/li&gt;
&lt;li&gt;Deliver When Users Need It&lt;/li&gt;
&lt;li&gt;Use Version Control to Drive Builds, Tests and Releases&lt;/li&gt;
&lt;li&gt;Test Early, Test Often, Test Automatically&lt;/li&gt;
&lt;li&gt;Coding Ain&amp;rsquo;t Done &amp;lsquo;Til All the Tests Run&lt;/li&gt;
&lt;li&gt;Use Saboteurs to Test Your Testing&lt;/li&gt;
&lt;li&gt;Test State Coverage, Not Code Coverage&lt;/li&gt;
&lt;li&gt;Find Bugs Once&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t use Manual Procedures&lt;/li&gt;
&lt;li&gt;Delight Users, Don&amp;rsquo;t Just Deliver Code&lt;/li&gt;
&lt;li&gt;Sign your Work&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="professional-ethics"&gt;Professional Ethics&lt;a class="anchor ms-1" href="#professional-ethics" aria-label="Permalink: Professional Ethics"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol start="98"&gt;
&lt;li&gt;First, Do No Harm&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t Enable Scumbags&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s Your Life&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Understand and handle .NET exceptions</title><link>https://blog-dev.funkysi1701.com/posts/2025/exceptions/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 17 Feb 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/exceptions/</guid><category term="Exceptions">Exceptions</category><category term="DotNet">DotNet</category><category term="ErrorHandling">ErrorHandling</category><category term="Programming">Programming</category><category term="Development">Development</category><category term="Tech">Tech</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/exceptions.png"/><description>&lt;p&gt;While at &lt;a href="https://blog-dev.funkysi1701.com/posts/2025/volunteering-at-ndc/"&gt;NDC London&lt;/a&gt;
I heard a great talk by &lt;a href="https://mattburke.dev/talks/youre-doing-exceptions-wrong/" target="_blank" rel="noopener noreferrer"&gt;Matt Burke&lt;/a&gt;
about exceptions. I will share some of the points here.&lt;/p&gt;
&lt;p&gt;The talk was based in part on the blog post &lt;a href="https://ericlippert.com/2008/09/10/vexing-exceptions/" target="_blank" rel="noopener noreferrer"&gt;Vexing Exceptions&lt;/a&gt;
by Eric Lippert.&lt;/p&gt;
&lt;p&gt;It defined four types of exceptions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fatal Exceptions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boneheaded Exceptions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vexing Exceptions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exogenous Exceptions&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="fatal-exceptions"&gt;Fatal Exceptions&lt;a class="anchor ms-1" href="#fatal-exceptions" aria-label="Permalink: Fatal Exceptions"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are exceptions that are thrown when the application is in an unrecoverable state. Examples of these are OutOfMemoryException and StackOverflowException. Don&amp;rsquo;t try and catch these kind of exceptions, just let the application crash.&lt;/p&gt;
&lt;h2 id="boneheaded-exceptions"&gt;Boneheaded Exceptions&lt;a class="anchor ms-1" href="#boneheaded-exceptions" aria-label="Permalink: Boneheaded Exceptions"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are exceptions that are caused by a bug in the code. These are the easiest to fix, as you just need to fix the bug. Examples of these are NullReferenceException and IndexOutOfRangeException. Boneheaded exceptions are a gift and it is the code telling you, that you have a bug to fix. Do throw these kind of exceptions, when someone messes up. They can be especially useful when someone else is consuming your API, you can throw an exception with a message that tells them what they did wrong.&lt;/p&gt;
&lt;h2 id="vexing-exceptions"&gt;Vexing Exceptions&lt;a class="anchor ms-1" href="#vexing-exceptions" aria-label="Permalink: Vexing Exceptions"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are exceptions that are thrown by the framework, and are hard to avoid. Examples of these are FormatException and InvalidOperationException. These are the hardest to fix, as they are thrown by the framework. You can&amp;rsquo;t fix the framework, so you have to work around them. You can&amp;rsquo;t avoid them, so you have to catch them. You can&amp;rsquo;t ignore them, so you have to handle them. You can&amp;rsquo;t fix them, so you have to work around them.&lt;/p&gt;
&lt;p&gt;One example of this is the int.Parse() method, it will throw a FormatException if the string is not a valid integer. You can&amp;rsquo;t avoid this, so you have to catch it. Of course a better way to do this is to use int.TryParse() which will return a boolean if the parse was successful.&lt;/p&gt;
&lt;h2 id="exogenous-exceptions"&gt;Exogenous Exceptions&lt;a class="anchor ms-1" href="#exogenous-exceptions" aria-label="Permalink: Exogenous Exceptions"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are exceptions that are thrown by external systems. Examples of these are IOException and SocketException. These are the hardest to fix, as they are thrown by external systems. You can&amp;rsquo;t fix the external system, so you have to work around them.&lt;/p&gt;
&lt;p&gt;A recent example is concurrency problems, if you are processing data in a database and other systems could update it at the same time, you could get a concurrency exception. The data you have in memory might not reflect the real state of that data. You can&amp;rsquo;t avoid this, so you have to catch it. You can&amp;rsquo;t ignore it, so you have to handle it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Do something that could throw an exception&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Log the exception&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Log.Error(ex);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; ex; &lt;span style="color:#75715e"&gt;// Bad resets the stack trace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// Good keeps the stack trace &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also don&amp;rsquo;t hide Exogenous Exceptions, if you catch an exception and don&amp;rsquo;t rethrow it, you are hiding the exception from the caller. I have seen code that uses the mediator pattern, it will call a method and not return to the original caller that something went wrong. This is bad, as the original caller needs to know that something went wrong.&lt;/p&gt;
&lt;p&gt;If you have enjoyed this article and want to get a monthly email with all my latest articles, please sign up for my &lt;a href="https://blog-dev.funkysi1701.com/newsletter"&gt;newsletter&lt;/a&gt;
. If you have any questions or comments, please feel free to reach out or leave a comment below.&lt;/p&gt;</description></item><item><title>What is new with C#</title><link>https://blog-dev.funkysi1701.com/posts/2025/whats-new-csharp/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 10 Feb 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/whats-new-csharp/</guid><category term="C-Sharp">C-Sharp</category><category term="Programming">Programming</category><category term="DotNet">DotNet</category><category term="LanguageFeatures">LanguageFeatures</category><category term="Development">Development</category><category term="Tech">Tech</category><media:content medium="image" type="image/jpeg" url="https://blog-dev.funkysi1701.com/images/mads-csharp.jpg"/><description>&lt;p&gt;I was lucky enough to hear Mads Torgersen (The Lead Designer of the C# language) speak at &lt;a href="https://blog-dev.funkysi1701.com/posts/2025/volunteering-at-ndc/"&gt;NDC London&lt;/a&gt;
about some of the plans the team has for the next version of C#. Many of these new features are in the preview stage, and can be enabled by using the c# preview flag in your csproj file. The C# team welcomes feedback on the different use cases for new feature, especially during these early previews. As this is a preview of new language features, don&amp;rsquo;t blame me if this all changes before it all ships in Novembers release!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;LangVersion&amp;gt;&lt;/span&gt;preview&lt;span style="color:#f92672"&gt;&amp;lt;/LangVersion&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="dictionary-expressions"&gt;Dictionary Expressions&lt;a class="anchor ms-1" href="#dictionary-expressions" aria-label="Permalink: Dictionary Expressions"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is an extension of the existing collection expressions that we have had since C# 12, but this time for dictionaries.&lt;/p&gt;
&lt;p&gt;The syntax is going to look a bit like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IList&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; foo = [&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IDictionary&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt; bar = [&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;one&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;two&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;three&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md" target="_blank" rel="noopener noreferrer"&gt;Dictionary Expressions&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id="simple-lambda-parameters-with-modifiers"&gt;Simple lambda parameters with modifiers&lt;a class="anchor ms-1" href="#simple-lambda-parameters-with-modifiers" aria-label="Permalink: Simple lambda parameters with modifiers"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WithOut w1 = (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; s) =&amp;gt; s.Length;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WithOut w2 = (s) =&amp;gt; s.Length;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WithOut w3 = (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; s, &lt;span style="color:#66d9ef"&gt;out&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i) =&amp;gt; i = s.Length;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WithOut w4 = (s, &lt;span style="color:#66d9ef"&gt;out&lt;/span&gt; i) =&amp;gt; i = s.Length;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/simple-lambda-parameters-with-modifiers.md" target="_blank" rel="noopener noreferrer"&gt;Simple lambda parameters with modifiers&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id="unbound-generic-types-in-nameof"&gt;Unbound generic types in nameof&lt;a class="anchor ms-1" href="#unbound-generic-types-in-nameof" aria-label="Permalink: Unbound generic types in nameof"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; t = &lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(List&amp;lt;&amp;gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; n = nameof(List&amp;lt;&amp;gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/unbound-generic-types-in-nameof.md" target="_blank" rel="noopener noreferrer"&gt;Unbound generic types in nameof&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id="null-conditional-assignment"&gt;Null conditional assignment&lt;a class="anchor ms-1" href="#null-conditional-assignment" aria-label="Permalink: Null conditional assignment"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; c = C.GetOne();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c.P = &lt;span style="color:#e6db74"&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c.E += () =&amp;gt; { };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c?.P = &lt;span style="color:#e6db74"&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c?.E += () =&amp;gt; { };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/null-conditional-assignment.md" target="_blank" rel="noopener noreferrer"&gt;Null conditional assignment&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id="partial-events-and-constructors"&gt;Partial events and constructors&lt;a class="anchor ms-1" href="#partial-events-and-constructors" aria-label="Permalink: Partial events and constructors"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;partial&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Partials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;partial&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; M(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;partial&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; P { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;partial&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i] { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;partial&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;event&lt;/span&gt; Handler? E;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;partial&lt;/span&gt; Partials();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/partial-events-and-constructors.md" target="_blank" rel="noopener noreferrer"&gt;Partial events and constructors&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id="field-access-in-auto-properties"&gt;Field access in auto properties&lt;a class="anchor ms-1" href="#field-access-in-auto-properties" aria-label="Permalink: Field access in auto properties"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Fields&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string?&lt;/span&gt; FirstName { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string?&lt;/span&gt; LastName { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt; =&amp;gt; field; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt; =&amp;gt; field = &lt;span style="color:#66d9ef"&gt;value&lt;/span&gt;?.Trim(); }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;note the field keyword is a potential breaking change if you have field variables in your code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/field-keyword.md" target="_blank" rel="noopener noreferrer"&gt;Field access in auto properties&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;This last one revealed a couple of gems of information that I found interesting. Creating a class called var can essentially disable the var keyword, and if you declare a variable called _ it can disable the discard operator. Neither of these things I would want to do in production code, but interesting that the language allows them.&lt;/p&gt;
&lt;h2 id="extensions"&gt;Extensions&lt;a class="anchor ms-1" href="#extensions" aria-label="Permalink: Extensions"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We have had extension methods for a while, but what about extension members?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/extensions.md" target="_blank" rel="noopener noreferrer"&gt;Extension members&lt;/a&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Enumerable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// New extension declaration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extension(IEnumerable source) { ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Classic extension method&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; IEnumerable&amp;lt;TResult&amp;gt; Cast&amp;lt;TResult&amp;gt;(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; IEnumerable source) { ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Non-extension member&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; IEnumerable&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; Range(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; start, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; count) { ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This was a fascinating look at what is coming up for C#, and it will be interesting to see how these new ways of writing code will get adopted.&lt;/p&gt;
&lt;p&gt;If you have enjoyed this article and want to get a monthly email with all my latest articles, please sign up for my &lt;a href="https://blog-dev.funkysi1701.com/newsletter"&gt;newsletter&lt;/a&gt;
. If you have any questions or comments, please feel free to reach out or leave a comment below.&lt;/p&gt;</description></item><item><title>Volunteering at NDC London</title><link>https://blog-dev.funkysi1701.com/posts/2025/volunteering-at-ndc/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 03 Feb 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/volunteering-at-ndc/</guid><category term="NDCLondon">NDCLondon</category><category term="Volunteering">Volunteering</category><category term="Conference">Conference</category><category term="DotNet">DotNet</category><category term="TechEvents">TechEvents</category><category term="Networking">Networking</category><category term="SoftwareDevelopment">SoftwareDevelopment</category><media:content medium="image" type="image/jpeg" url="https://blog-dev.funkysi1701.com/images/ndc-simon.jpg"/><description>&lt;p&gt;
&lt;img class="img-fluid" alt="The NDC Volunteers" src="https://blog-dev.funkysi1701.com/images/ndc-crew.jpg" loading="lazy"
width="2048" height="865"
/&gt;
&lt;/p&gt;
&lt;p&gt;I have just come home from volunteering at NDC London. It was a great experience and I learned a lot. I met some great people and had a lot of fun. I can&amp;rsquo;t wait to go back next year.&lt;/p&gt;
&lt;p&gt;You may have heard of &lt;a href="https://ndclondon.com/" target="_blank" rel="noopener noreferrer"&gt;NDC London&lt;/a&gt;
before. It is a conference for software developers. It is held in London every year and attracts people from all over the world. The conference covers a wide range of topics, with a strong focus on .NET. I have heard about the conference for years and always wanted to go, but with ticket prices in the thousands, it was out of reach for me.&lt;/p&gt;
&lt;p&gt;Late last year, I saw a contact on social media mention that &lt;a href="https://ndclondon.com/volunteer" target="_blank" rel="noopener noreferrer"&gt;volunteering at NDC&lt;/a&gt;
was a good way to go to the conference for free. I had heard that this was an option before, but hadn&amp;rsquo;t acted upon it. So I sent a quick email to the conference organizers. I didn&amp;rsquo;t expect I would get on the team, but they emailed back and said I was on the team, and asked what was my t-shirt size. I was so excited!&lt;/p&gt;
&lt;p&gt;The event is held at the &lt;a href="https://qeiicentre.london/" target="_blank" rel="noopener noreferrer"&gt;Queen Elizabeth II Centre&lt;/a&gt;
, which is a great venue, right next to Big Ben and Westminster Abbey. The conference is spread over a whole week, with 2 days of workshops and 3 days of talks. Volunteers are only needed for the 3 days of talks, however, there was an intro meeting the day before.&lt;/p&gt;
&lt;p&gt;As a volunteer, you don&amp;rsquo;t get paid, so I needed to arrange my own transport to London and accommodation. It being London, I knew the tube network was a quick and easy way to get around, so I could search for a reasonably priced hotel that was near the venue.&lt;/p&gt;
&lt;p&gt;The intro meeting was on Tuesday evening, at 5.30 pm. I arrived a bit early and unfortunately, I wasn&amp;rsquo;t very clear about why I was there so I was told to wait and the meetup would go upstairs soon. The London .NET user group was having a session at 5 pm with Mads Torgersen (the C# language designer), but luckily I double-checked and got to wait with the right people. However, this was not a bad thing, as I got to see some of the .NET team walk past, like Steve Sanderson and of course Mads Torgersen who was going to the meetup.&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="London view" src="https://blog-dev.funkysi1701.com/images/london.jpg" loading="lazy"
width="2000" height="900"
/&gt;
&lt;/p&gt;
&lt;p&gt;Once the intro meeting started, we unboxed the hoodies that every attendee was given. These ranged in size from XS to XXXL, we also learned how the registration process worked. Scan a QR code, print off the ticket, stick it to the lanyard, and issue it to the attendee. I also found out that there was a decent Yorkshire contingent, some I had met from York and Leeds user groups. We had a tour of the venue, the conference was on floors 3, 4, and 5. The top floor had a great view of Big Ben and Westminster Abbey. We were also issued our own T-shirt and hoodies, which were purple and said NDC Crew on them along with our lanyard passes.&lt;/p&gt;
&lt;p&gt;It was an early morning, we needed to be at the venue for 7 am, ready to welcome 900+ people to the conference when the doors opened at 8 am. I helped man the hoodies, handing out XXL to any attendees who wanted them. There was a steady stream of people registering, however, I didn&amp;rsquo;t feel overwhelmed. By the time we had finished at registration, the keynote with Scott Hanselman was about halfway through, so I went to get a coffee and some breakfast and had a look at the schedule for the day. I was assigned to help out with the afternoon sessions so I was free to attend any sessions I liked in the morning.&lt;/p&gt;
&lt;p&gt;For my afternoon duties, I needed to report back when the speaker had arrived and count up the number of green/amber/red cards for the session. At NDC feedback for the different speakers is submitted by the attendees by putting cards of different colours in a box. Green for good, amber for ok, red for bad. Throughout the different talks I attended there was always a good proportion of green cards, indicating the high quality of the speakers at NDC.&lt;/p&gt;
&lt;img src="https://blog-dev.funkysi1701.com/images/ndc-simon.jpg" alt="The NDC Volunteers" width="250px" style="float:left;padding-right: 15px; " /&gt;
&lt;p&gt;Wearing a purple NDC Crew hoodie, gave me a certain amount of confidence, I walked among many of the sponsors and chatted to a few. Elastic Search had a stall, and I mentioned my recent experience at work with them. At some conferences, I can feel a bit out of place, but at NDC being a volunteer, I had an automatic group of friends to chat to, the other volunteers, and this also helped me speak with one or two of the speakers. I enjoyed saying hello to Richard Campbell, who I had listened to on the .Net Rocks and RunAs Radio podcasts for years. I told him he had sent me a mug, and he mentioned it was a collector&amp;rsquo;s item now.&lt;/p&gt;
&lt;p&gt;At the end of the first day, I was invited to the speaker dinner, which was a great experience. I got to chat with some of the other volunteers. I had a great time and it was free food and drink so I can&amp;rsquo;t complain.&lt;/p&gt;
&lt;p&gt;While I was volunteering, I was often hanging out with the other volunteers. We had a great time, and I made some new friends. We had a lot of fun, and I learned a lot, which I hope to blog about soon. I can&amp;rsquo;t wait to go back next year.&lt;/p&gt;
&lt;p&gt;If you have enjoyed this article and want to get a monthly email with all my latest articles, please sign up for my &lt;a href="https://blog-dev.funkysi1701.com/newsletter"&gt;newsletter&lt;/a&gt;
. If you have any questions or comments, please feel free to reach out or leave a comment below.&lt;/p&gt;</description></item><item><title>Set up Grafana for .NET with Docker</title><link>https://blog-dev.funkysi1701.com/posts/2025/setting-up-grafana/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 27 Jan 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/setting-up-grafana/</guid><category term="Grafana">Grafana</category><category term="Monitoring">Monitoring</category><category term="Analytics">Analytics</category><category term="Docker">Docker</category><category term="Prometheus">Prometheus</category><category term="DotNet">DotNet</category><category term="DevOps">DevOps</category><category term="Metrics">Metrics</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/grafana-dashboard.png"/><description>&lt;p&gt;Grafana is an open-source analytics and monitoring platform that lets you query, visualise, and alert on metrics. It pairs well with Prometheus for scraping application endpoints. This post walks through a Docker Compose stack that monitors a simple .NET API.&lt;/p&gt;
&lt;h2 id="setting-up-grafana"&gt;Setting up Grafana&lt;a class="anchor ms-1" href="#setting-up-grafana" aria-label="Permalink: Setting up Grafana"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To visualise metrics data, we need a few components. The .NET application exposes a &lt;code&gt;/metrics&lt;/code&gt; endpoint. Prometheus scrapes it on an interval and stores the samples. Grafana then queries Prometheus and renders dashboards.&lt;/p&gt;
&lt;h3 id="docker-compose-file"&gt;Docker Compose File&lt;a class="anchor ms-1" href="#docker-compose-file" aria-label="Permalink: Docker Compose File"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Start with a Compose file for Prometheus and Grafana. Pin image tags so rebuilds stay reproducible (bump them when you deliberately upgrade):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;prometheus&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;prom/prometheus:v3.4.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;5431&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./prometheus/:/etc/prometheus/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;prometheus:/prometheus &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;grafana&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana/grafana:11.6.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;expose&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;3000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;grafana:/var/lib/grafana&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;prometheus&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;grafana&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This brings up Grafana and Prometheus, maps ports, and mounts config/data volumes.&lt;/p&gt;
&lt;h3 id="prometheus-configuration"&gt;Prometheus Configuration&lt;a class="anchor ms-1" href="#prometheus-configuration" aria-label="Permalink: Prometheus Configuration"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Next, create a &lt;strong&gt;prometheus.yml&lt;/strong&gt; file to configure Prometheus:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;global&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;scrape_interval&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;15s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;evaluation_interval&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;15s &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;scrape_configs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;job_name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;api&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;scheme&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;https&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tls_config&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;insecure_skip_verify&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;scrape_interval&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;scrape_timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;static_configs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;targets&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#39;api.example.com:443&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This configures how often the &lt;code&gt;/metrics&lt;/code&gt; endpoint should be scraped. By default Prometheus expects something like &lt;code&gt;http://api.example.com/metrics&lt;/code&gt;. Because my API ran under HTTPS I set &lt;code&gt;scheme&lt;/code&gt; to &lt;code&gt;https&lt;/code&gt; and skipped TLS verification in this lab setup. &lt;code&gt;scrape_interval&lt;/code&gt; is how often the endpoint is scraped; if &lt;code&gt;scrape_timeout&lt;/code&gt; is omitted the global value is used.&lt;/p&gt;
&lt;p&gt;More information about configuring Prometheus can be found &lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/" target="_blank" rel="noopener noreferrer"&gt;here&lt;/a&gt;
.&lt;/p&gt;
&lt;h3 id="grafana-config"&gt;Grafana Config&lt;a class="anchor ms-1" href="#grafana-config" aria-label="Permalink: Grafana Config"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can now log into Grafana on &lt;a href="http://localhost:3000" target="_blank" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;
with the default username and password of &lt;code&gt;admin&lt;/code&gt;/&lt;code&gt;admin&lt;/code&gt;. You will be prompted to change the password. Add a Prometheus data source next. Prometheus is published on the host as &lt;a href="http://localhost:5431" target="_blank" rel="noopener noreferrer"&gt;http://localhost:5431&lt;/a&gt;
, but from another container you usually cannot use &lt;code&gt;localhost&lt;/code&gt; for a sibling service.&lt;/p&gt;
&lt;p&gt;If Grafana needs to reach Prometheus on the &lt;strong&gt;host&lt;/strong&gt; (for example when Prometheus is published only to the host network), use &lt;strong&gt;host.docker.internal&lt;/strong&gt; (Docker Desktop and many Linux setups with the host-gateway mapping): &lt;a href="http://host.docker.internal:5431" target="_blank" rel="noopener noreferrer"&gt;http://host.docker.internal:5431&lt;/a&gt;
. Prefer the Compose service name (&lt;code&gt;http://prometheus:9090&lt;/code&gt;) when both services share the same Compose network.&lt;/p&gt;
&lt;h3 id="dashboard"&gt;Dashboard&lt;a class="anchor ms-1" href="#dashboard" aria-label="Permalink: Dashboard"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The next thing we need to do is build a dashboard — the .NET team has done much of the hard work. They published a &lt;a href="https://devblogs.microsoft.com/dotnet/introducing-aspnetcore-metrics-and-grafana-dashboards-in-dotnet-8/" target="_blank" rel="noopener noreferrer"&gt;Grafana dashboard&lt;/a&gt;
you can import.&lt;/p&gt;
&lt;p&gt;You can also build panels yourself in the Grafana UI (graphs, gauges, and other visualisations).&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Dashboard" src="https://blog-dev.funkysi1701.com/images/2025/dashboard-screenshot.png" loading="lazy"
width="2471" height="1238"
/&gt;
&lt;/p&gt;
&lt;h2 id="metrics-endpoint"&gt;Metrics endpoint&lt;a class="anchor ms-1" href="#metrics-endpoint" aria-label="Permalink: Metrics endpoint"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If your .NET application is using .NET Aspire, this &lt;code&gt;/metrics&lt;/code&gt; endpoint is often already set up for you. Look in &lt;code&gt;Extensions.cs&lt;/code&gt; in your ServiceDefaults project for something like the following (you need the NuGet package &lt;code&gt;OpenTelemetry.Exporter.Prometheus.AspNetCore&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; WebApplication MapDefaultEndpoints(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; WebApplication app)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// The following line enables the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.MapPrometheusScrapingEndpoint();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you are not using .NET Aspire, you can add &lt;code&gt;app.MapPrometheusScrapingEndpoint()&lt;/code&gt; to your &lt;code&gt;Program.cs&lt;/code&gt; file (with the same package referenced).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With Compose, Prometheus, and Grafana in place you can scrape a .NET &lt;code&gt;/metrics&lt;/code&gt; endpoint and watch live dashboards — useful for latency, exceptions, and ASP.NET Core built-in meters. Happy monitoring!&lt;/p&gt;
&lt;h3 id="related-on-this-blog"&gt;Related on this blog&lt;a class="anchor ms-1" href="#related-on-this-blog" aria-label="Permalink: Related on this blog"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prefer classic host and service checks? Try &lt;a href="https://blog-dev.funkysi1701.com/posts/2025/monitoring-with-nagios-docker/"&gt;monitoring with Nagios in Docker&lt;/a&gt;
. If Aspire already wires OpenTelemetry for you, &lt;a href="https://blog-dev.funkysi1701.com/posts/2024/aspire/"&gt;getting started with Aspire&lt;/a&gt;
is a useful companion to the metrics endpoint notes above.&lt;/p&gt;
&lt;p&gt;If you have enjoyed this article and want to get a monthly email with all my latest articles, please sign up to my &lt;a href="https://blog-dev.funkysi1701.com/newsletter"&gt;newsletter&lt;/a&gt;
. If you have any questions or comments, please feel free to reach out or leave a comment below.&lt;/p&gt;</description></item><item><title>Adding Elasticsearch with .Net Aspire</title><link>https://blog-dev.funkysi1701.com/posts/2025/adding-elasticsearch-with-aspire/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 20 Jan 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/adding-elasticsearch-with-aspire/</guid><category term="Elasticsearch">Elasticsearch</category><category term="DotNet">DotNet</category><category term="Aspire">Aspire</category><category term="Search">Search</category><category term="Indexing">Indexing</category><category term="Development">Development</category><category term="Tech">Tech</category><media:content medium="image" type="image/jpeg" url="https://blog-dev.funkysi1701.com/images/elastic-search.jpg"/><description>&lt;p&gt;A few weeks ago I talked about &lt;a href="https://blog-dev.funkysi1701.com/posts/2024/aspire/"&gt;.Net Aspire&lt;/a&gt;
, well lets take a look at how we can use it with Elasticsearch. Elastic search is a powerful search engine that can be used to index and search data. It is built on top of Lucene and provides a powerful way to search through your data.&lt;/p&gt;
&lt;p&gt;Aspire allows dependencies like elasticsearch to be added to a project programmatically. Lets look at how this can work.&lt;/p&gt;
&lt;p&gt;Add this nuget package to your AppHost project (as of writing this is a preview package):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Aspire.Hosting.Elasticsearch&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;9.0.0-preview.5.24551.3&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In your AppHost project, add the following code to your &lt;code&gt;Program.cs&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; elasticsearch = builder
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddElasticsearch(&lt;span style="color:#e6db74"&gt;&amp;#34;elasticsearch&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now if you run your project it will add an elasticsearch containter to your project. A common way to administer elasticsearch is to use Kibana. You can add this to your project by adding the following code to your &lt;code&gt;Program.cs&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; kibana = builder
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddContainer(&lt;span style="color:#e6db74"&gt;&amp;#34;kibana&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;kibana&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;8.15.3&amp;#34;&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// Add Kibana from the image kibana, and tag 8.15.3 and give it a name kibana&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithReference(elasticsearch) &lt;span style="color:#75715e"&gt;// Add a reference to the elasticsearch container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithEndpoint(&lt;span style="color:#ae81ff"&gt;5601&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5601&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Expose a port so you can connect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now if you run your project you will have an elasticsearch and kibana container running. You can access kibana by going to &lt;code&gt;http://localhost:5601&lt;/code&gt;, and elasticsearch by going to &lt;code&gt;http://localhost:9200&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="troubleshooting-kibana"&gt;Troubleshooting Kibana&lt;a class="anchor ms-1" href="#troubleshooting-kibana" aria-label="Permalink: Troubleshooting Kibana"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;However when I do that I get the error &lt;code&gt;Kibana server is not ready yet&lt;/code&gt;. Looking at the kibana container logs, I see the following error:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;2025-01-19T21:42:13 [2025-01-19T21:42:13.558+00:00][WARN ][plugins.security.config] Generating a random key for xpack.security.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.security.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.
Unable to retrieve version information from Elasticsearch nodes. security_exception
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is due to the security settings in Elasticsearch. To fix this, you can turn off this security feature by enabling an environment variable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; elasticsearch = builder
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddElasticsearch(&lt;span style="color:#e6db74"&gt;&amp;#34;elasticsearch&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .WithEnvironment(&lt;span style="color:#e6db74"&gt;&amp;#34;xpack.security.enabled&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As Aspire is only running locally, this is fine, but in a production environment, you would want to secure your Elasticsearch and Kibana instances.&lt;/p&gt;
&lt;h2 id="using-elasticsearch-in-your-project"&gt;Using Elasticsearch in Your Project&lt;a class="anchor ms-1" href="#using-elasticsearch-in-your-project" aria-label="Permalink: Using Elasticsearch in Your Project"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you have Elasticsearch added to your project, how can you use it? In the .NET project that wants to connect and interact with Elasticsearch, you can add the following NuGet package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Aspire.Elastic.Clients.Elasticsearch (version 9.0.0-preview.5.24551.3 as of writing)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the &lt;code&gt;Program.cs&lt;/code&gt; file of your .NET project you can add the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.AddElasticsearchClient(&lt;span style="color:#e6db74"&gt;&amp;#34;elasticsearch&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No need to specify any complex connection strings or settings, Aspire will handle all of that for you.&lt;/p&gt;
&lt;p&gt;This is pretty much all of the Aspire specific code you need, but lets have a quick look at indexing some data and returning it with a search. Elastic Search has extensive &lt;a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/examples.html" target="_blank" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;
on how to do this with the dotnet libraries.&lt;/p&gt;
&lt;p&gt;To index some data you can use the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _client.Indices.CreateAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;searchindex&amp;#34;&lt;/span&gt;, cancellationToken); &lt;span style="color:#75715e"&gt;// Create an index called searchindex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; documents = context.Episode.ToList(); &lt;span style="color:#75715e"&gt;// Get the documents you want to index from Entity Framework or any other source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _client.IndexManyAsync(documents, index: &lt;span style="color:#e6db74"&gt;&amp;#34;searchindex&amp;#34;&lt;/span&gt;, cancellationToken); &lt;span style="color:#75715e"&gt;// Index the documents into the searchindex index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ElasticsearchClient can be injected in via dependency injection, again no need to specify any connection strings or settings.&lt;/p&gt;
&lt;p&gt;To search for data you can use the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Create a search request with a wildcard query which searches in the title field of your indexed documents.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; searchRequest = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ESSearchRequest(&lt;span style="color:#e6db74"&gt;&amp;#34;searchindex&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; From = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Size = &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Query = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; WildcardQuery(&lt;span style="color:#e6db74"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;) { Value = &lt;span style="color:#e6db74"&gt;$&amp;#34;*{SearchQuery}*&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Execute the search request and get the response.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _client.SearchAsync&amp;lt;Episode&amp;gt;(searchRequest, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (response.IsValidResponse)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Return the documents from the response.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; response.Documents.ToList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By following these steps, you can successfully integrate Elasticsearch and Kibana into your .NET Aspire project. This setup allows you to leverage powerful search and analytics capabilities in your application. Remember to secure your instances in a production environment to ensure data safety.&lt;/p&gt;</description></item><item><title>NuGet central package management</title><link>https://blog-dev.funkysi1701.com/posts/2025/nuget-central-package-management/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 13 Jan 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/nuget-central-package-management/</guid><category term="NuGet">NuGet</category><category term="PackageManagement">PackageManagement</category><category term="DotNet">DotNet</category><category term="Development">Development</category><category term="DevOps">DevOps</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/central-package-management-with-nuget.png"/><description>&lt;p&gt;Managing dependencies in .NET projects can become complex, especially when dealing with multiple projects that share common packages. NuGet Central Package Management (CPM) is a feature that simplifies this process by allowing you to manage package versions centrally.&lt;/p&gt;
&lt;h2 id="what-is-nuget-central-package-management"&gt;What is NuGet Central Package Management?&lt;a class="anchor ms-1" href="#what-is-nuget-central-package-management" aria-label="Permalink: What is NuGet Central Package Management?"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;NuGet Central Package Management allows you to define and manage package versions in a central location, rather than specifying them individually in each project file. This approach ensures consistency across projects and simplifies the process of updating package versions.&lt;/p&gt;
&lt;h2 id="setting-up-central-package-management"&gt;Setting Up Central Package Management&lt;a class="anchor ms-1" href="#setting-up-central-package-management" aria-label="Permalink: Setting Up Central Package Management"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To get started with Central Package Management, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a Directory.Packages.props File&lt;/strong&gt;: This file will contain the central package version definitions. Create this file in the root directory of your solution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Define Package Versions&lt;/strong&gt;: In the &lt;code&gt;Directory.Packages.props&lt;/code&gt; file, define the package versions you want to manage centrally. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ManagePackageVersionsCentrally&amp;gt;&lt;/span&gt;true&lt;span style="color:#f92672"&gt;&amp;lt;/ManagePackageVersionsCentrally&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageVersion&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;13.0.1&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageVersion&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Serilog&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;2.10.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageVersion&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.EntityFrameworkCore&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;9.0.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageVersion&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.EntityFrameworkCore.SqlServer&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;9.0.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Update all your csproj files&lt;/strong&gt;: In each project file (&lt;code&gt;.csproj&lt;/code&gt;), remove the version number information:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Serilog&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.EntityFrameworkCore&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.EntityFrameworkCore.SqlServer&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="benefits-of-central-package-management"&gt;Benefits of Central Package Management&lt;a class="anchor ms-1" href="#benefits-of-central-package-management" aria-label="Permalink: Benefits of Central Package Management"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using Central Package Management offers several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: Ensures that all projects use the same package versions, reducing the risk of version conflicts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplified Updates&lt;/strong&gt;: Updating a package version in the central file automatically updates it across all projects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced Maintenance&lt;/strong&gt;: Simplifies the process of managing package versions, especially in large solutions with many projects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If for any reason you want to disable Central Package Management, you can set the &lt;code&gt;ManagePackageVersionsCentrally&lt;/code&gt; property to &lt;code&gt;false&lt;/code&gt; in the &lt;code&gt;Directory.Packages.props&lt;/code&gt; file, or in the csproj file if you only want to disable for a single project.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;NuGet Central Package Management simplifies the process of managing package versions across multiple projects in a .NET solution. By defining package versions in a central file, you can ensure consistency, simplify updates, and reduce maintenance efforts. Give it a try in your next .NET project to experience the benefits firsthand.&lt;/p&gt;
&lt;p&gt;For more information, visit the &lt;a href="https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management" target="_blank" rel="noopener noreferrer"&gt;official NuGet documentation&lt;/a&gt;
.&lt;/p&gt;</description></item><item><title>Setting Up Nagios Monitoring with Docker</title><link>https://blog-dev.funkysi1701.com/posts/2025/monitoring-with-nagios-docker/</link><author>funkysi1701@gmail.com (funkysi1701)</author><pubDate>Mon, 06 Jan 2025 20:00:00 +0000</pubDate><guid>https://blog-dev.funkysi1701.com/posts/2025/monitoring-with-nagios-docker/</guid><category term="Nagios">Nagios</category><category term="Docker">Docker</category><category term="Monitoring">Monitoring</category><category term="SysAdmin">SysAdmin</category><category term="DevOps">DevOps</category><category term="NetworkMonitoring">NetworkMonitoring</category><media:content medium="image" type="image/png" url="https://blog-dev.funkysi1701.com/images/nagios.png"/><description>&lt;p&gt;A long time ago in my SysAdmin days, I was a big fan of &lt;a href="https://blog-dev.funkysi1701.com/posts/2014/i-love-nagios/"&gt;Nagios&lt;/a&gt;
. Recently, I decided to revisit it and see if I could run it in a Docker container to monitor my local network. This guide will walk you through the process of setting up Nagios monitoring using Docker.&lt;/p&gt;
&lt;h2 id="finding-a-docker-image-for-nagios"&gt;Finding a Docker Image for Nagios&lt;a class="anchor ms-1" href="#finding-a-docker-image-for-nagios" aria-label="Permalink: Finding a Docker Image for Nagios"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, we need to find a suitable Docker image for Nagios. The &lt;a href="https://hub.docker.com/r/manios/nagios" target="_blank" rel="noopener noreferrer"&gt;manios/nagios&lt;/a&gt;
image looks like it should do the job.&lt;/p&gt;
&lt;h2 id="running-the-nagios-docker-container"&gt;Running the Nagios Docker Container&lt;a class="anchor ms-1" href="#running-the-nagios-docker-container" aria-label="Permalink: Running the Nagios Docker Container"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The basics of running this container are as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --name nagios -p 8080:80 manios/nagios
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command will pull the Nagios image from Docker Hub and run it in a container, mapping port 80 in the container to port 8080 on your host machine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --name nagios &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v /path-to-nagios/etc/:/opt/nagios/etc/ &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v /path-to-nagios/var:/opt/nagios/var/ &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v /path-to-nagios/ssmtp.conf:/etc/ssmtp/ssmtp.conf &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v /path-to-custom-plugins:/opt/Custom-Nagios-Plugins &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -p 8080:80 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; manios/nagios
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I prefer running via docker compose, which looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nagios&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;manios/nagios:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;monitoring_nagios&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;8081&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./nagios/localhost.cfg:/opt/nagios/etc/objects/localhost.cfg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./nagios/windows.cfg:/opt/nagios/etc/objects/windows.cfg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;nagios_data:/opt/nagios/var/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;NAGIOSADMIN_USER=nagiosadmin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;NAGIOSADMIN_PASS=nagiosadmin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;NAGIOS_TIMEZONE=UTC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;NAGIOS_WEB_USER=nagiosadmin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;NAGIOS_WEB_PASS=nagiosadmin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="accessing-the-nagios-web-interface"&gt;Accessing the Nagios Web Interface&lt;a class="anchor ms-1" href="#accessing-the-nagios-web-interface" aria-label="Permalink: Accessing the Nagios Web Interface"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once the container is running, you can access the Nagios web interface by navigating to http://localhost:8080 in your web browser. You should see the Nagios login page.&lt;/p&gt;
&lt;h2 id="configuring-nagios"&gt;Configuring Nagios&lt;a class="anchor ms-1" href="#configuring-nagios" aria-label="Permalink: Configuring Nagios"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To configure Nagios, you&amp;rsquo;ll need to edit the configuration files located in the container. You can do this by accessing the container&amp;rsquo;s shell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it nagios /bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From here, you can navigate to the Nagios configuration directory and make the necessary changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /opt/nagios/etc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="adding-hosts-and-services"&gt;Adding Hosts and Services&lt;a class="anchor ms-1" href="#adding-hosts-and-services" aria-label="Permalink: Adding Hosts and Services"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To monitor your local network, you&amp;rsquo;ll need to add hosts and services to the Nagios configuration. This involves editing the configuration files and adding each host and service you want to monitor.&lt;/p&gt;
&lt;p&gt;I have added a few extra hosts to the localhost.cfg file to monitor my router, and a few other devices on my network. I have also added a Volume to store the nagios data, so after a restart of the container the data is still there.&lt;/p&gt;
&lt;p&gt;Lets have a look at how hosts and services are defined in the config files.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cfg" data-lang="cfg"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;define host{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;use linux-server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;host_name router&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;alias router&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;parents localhost; Hosts that this host is dependent on &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;address 192.168.2.1; IP Address or DNS name of the host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cfg" data-lang="cfg"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;define service{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;use local-service,graphed-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;host_name localhost, router&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;service_description PING&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check_command check_ping!100.0,20%!500.0,60%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When making changes to your config files it is useful to check the config is valid. To do this with the docker file you can run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it mynagioscontainer bin/nagios -v etc/nagios.cfg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It may have been 10 years since I last looked at Nagios, but the UI hasn&amp;rsquo;t changed much. Host are checked if they are Up or Down, and every Host can have a number of services associated with it and this are checked if they are Up or Down.&lt;/p&gt;
&lt;p&gt;
&lt;img class="img-fluid" alt="Nagios UI" src="https://blog-dev.funkysi1701.com/images/nagios.png" loading="lazy"
width="1605" height="702"
/&gt;
&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor ms-1" href="#conclusion" aria-label="Permalink: Conclusion"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By following these steps, you can set up Nagios monitoring using Docker to keep an eye on your local network. Nagios is a powerful tool for network monitoring, and running it in a Docker container makes it easy to deploy and manage. Happy monitoring!&lt;/p&gt;
&lt;h3 id="related-on-this-blog"&gt;Related on this blog&lt;a class="anchor ms-1" href="#related-on-this-blog" aria-label="Permalink: Related on this blog"&gt;&lt;i class="fas fa-link" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For application metrics and dashboards rather than host ping checks, see &lt;a href="https://blog-dev.funkysi1701.com/posts/2025/setting-up-grafana/"&gt;setting up Grafana&lt;/a&gt;
with Prometheus. I also wrote about &lt;a href="https://blog-dev.funkysi1701.com/posts/2014/i-love-nagios/"&gt;loving Nagios&lt;/a&gt;
the first time around — the UI has barely moved since.&lt;/p&gt;</description></item></channel></rss>