<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://jamesgould.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jamesgould.dev/" rel="alternate" type="text/html" /><updated>2025-08-23T13:28:04+00:00</updated><id>https://jamesgould.dev/feed.xml</id><title type="html">Practical .NET</title><author><name>James Gould</name><email>contact@jamesgould.dev</email></author><entry><title type="html">Introducing the Azure Key Vault Emulator</title><link href="https://jamesgould.dev/posts/Azure-Key-Vault-Emulator/" rel="alternate" type="text/html" title="Introducing the Azure Key Vault Emulator" /><published>2025-04-30T00:00:00+00:00</published><updated>2025-04-30T00:00:00+00:00</updated><id>https://jamesgould.dev/posts/Azure-Key-Vault-Emulator</id><content type="html" xml:base="https://jamesgould.dev/posts/Azure-Key-Vault-Emulator/"><![CDATA[<p><img src="https://jamesgould.dev/assets/images/kve-hero.png" alt="Azure Key Vault Emulator Introduction Hero" style="display:block; margin-left:auto; margin-right:auto" /></p>

<p>Developing applications that require secure storage of sensitive data is difficult but it’s the perfect use-case for Azure Key Vault. From API keys to entire certificates you can protect your sensitive data from being breached by opting for a battle-tested security product, but it can be a bit of a nightmare to develop an application for locally.</p>

<p>In a dev environment, currently, you need to have a <strong>real</strong> Azure Key Vault resource deployed and potentially being paid for in an active Azure subscription. If you’re like me and work for a fairly large company then the security policies around accessing these resources can be tough to navigate, meaning long delays during onboarding and potentially longer delays caused by multiple developers overwriting each other’s secure values.</p>

<p>Microsoft have put significant effort into making the cloud development experience easier with .NET and have released emulators for products that face the same issue. The Azure Service Bus now has an official <a href="https://learn.microsoft.com/en-us/azure/service-bus-messaging/overview-emulator">Emulator</a> to solve that problem for example, sadly Azure Key Vault does not have a similar alternative.</p>

<p>Or should I say… did not.</p>

<p><em>Dramatic pause.</em></p>

<p>The first stable release of the <a href="https://github.com/james-gould/azure-keyvault-emulator">Azure Key Vault Emulator</a> has shipped and is now available for public consumption. Here’s a quick rundown of what’s available:</p>

<ul>
  <li><strong>Full support for the Azure SDK Key Vault Clients</strong>, meaning you can use the official <code class="language-plaintext highlighter-rouge">SecretClient</code>, <code class="language-plaintext highlighter-rouge">KeyClient</code> and <code class="language-plaintext highlighter-rouge">CertificateClient</code> when using the Emulator and simply switch the <code class="language-plaintext highlighter-rouge">VaultURI</code> in production.</li>
  <li><strong>Native .NET Aspire support</strong> for both the hosting and client application. Using the Emulator prevents any provisioning of a real Azure Key Vault and doesn’t require any configuration to use, it Just Works™. Using <code class="language-plaintext highlighter-rouge">.NET Aspire</code> is not a requirement for the Emulator.</li>
  <li><strong>Session or persistent storage of secure data</strong>, meaning you can destroy all secure values when your application is finished running or keep them around if you don’t want to run any initialisation code.</li>
  <li><strong>No new dependencies</strong> because the Emulator is hosted externally to your main application (unless you use the handy <code class="language-plaintext highlighter-rouge">Client</code> library, more below).</li>
</ul>

<p>If you use, and like, the Emulator please make sure to ⭐ the repository. This engagement (amongst other criteria) is used to validate project applications for <a href="https://github.com/dotnet-foundation/projects/issues/441">.NET Foundation membership</a> which will guarantee long term support for the project. Thank you!</p>

<h2 id="getting-started-with-the-emulator">Getting started With the Emulator</h2>

<p>The first run of the Emulator will prompt you to install a <code class="language-plaintext highlighter-rouge">localhost</code> SSL certificate - this is unique to your machine and is required for the Azure SDK to work correctly, <a href="https://github.com/james-gould/azure-keyvault-emulator/blob/development/docs/CONFIG.md#configuring-your-local-system-for-the-emulator">click here if you want to learn more or provide your own certificates.</a></p>

<p>The Emulator runs as a <a href="https://hub.docker.com/r/jamesgoulddev/azure-keyvault-emulator">container</a> on your local machine and can be accessed by setting your <code class="language-plaintext highlighter-rouge">VaultUri</code> to <code class="language-plaintext highlighter-rouge">https://localhost:4997</code>.</p>

<p>If you’re using <code class="language-plaintext highlighter-rouge">.NET Aspire</code> there’s now built-in support for the emulator, meaning you can optionally override your  <code class="language-plaintext highlighter-rouge">IResourceBuilder&lt;AzureKeyVaultResource&gt;</code> to use the emulator.</p>

<p>First install the <a href="https://www.nuget.org/packages/AzureKeyVaultEmulator.Aspire.Hosting">AzureKeyVaultEmulator.Aspire.Hosting</a> package (.NET 8 or 9 only):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package AzureKeyVaultEmulator.Aspire.Hosting
</code></pre></div></div>

<p>Then add to or update your <code class="language-plaintext highlighter-rouge">AppHost</code> to run the Emulator:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">keyVaultResourceName</span> <span class="p">=</span> <span class="s">"keyvault"</span><span class="p">;</span>

<span class="c1">// With existing resource</span>
<span class="kt">var</span> <span class="n">keyVault</span> <span class="p">=</span> <span class="n">builder</span>
    <span class="p">.</span><span class="nf">AddAzureKeyVault</span><span class="p">(</span><span class="n">keyVaultResourceName</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">RunAsEmulator</span><span class="p">();</span> <span class="c1">// Add this line</span>

<span class="c1">// Or directly add the emulator as a resource, no configuration required</span>
<span class="kt">var</span> <span class="n">keyVault</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="nf">AddAzureKeyVaultEmulator</span><span class="p">(</span><span class="n">keyVaultResourceName</span><span class="p">);</span>

<span class="kt">var</span> <span class="n">webApi</span> <span class="p">=</span> <span class="n">builder</span>
    <span class="p">.</span><span class="n">AddProject</span><span class="p">&lt;</span><span class="n">Projects</span><span class="p">.</span><span class="n">MyApi</span><span class="p">&gt;(</span><span class="s">"api"</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">WithReference</span><span class="p">(</span><span class="n">keyVault</span><span class="p">);</span> <span class="c1">// reference as normal</span>
</code></pre></div></div>

<p>This will inject the environment variable <code class="language-plaintext highlighter-rouge">ConnectionStrings__keyvault</code>, where <code class="language-plaintext highlighter-rouge">keyvault</code> is whatever value you assigned to <code class="language-plaintext highlighter-rouge">keyVaultResourceName</code> above.</p>

<p>When using <code class="language-plaintext highlighter-rouge">.RunAsEmulator()</code> the resource will no longer attempt to provision in your Azure subscription; the <a href="https://www.reddit.com/r/dotnet/comments/1k7pr7l/comment/mp3ohum/">Aspire team specifically made this change</a> to support the Azure Key Vault Emulator which blew my mind.</p>

<p>One of the limitations of the Emulator is that it doesn’t pass the <code class="language-plaintext highlighter-rouge">ChallengeBasedAuthenticationPolicy</code> within the Azure SDK without changing your <code class="language-plaintext highlighter-rouge">hosts</code> file, this is due to the URL of the container not meeting the schema <code class="language-plaintext highlighter-rouge">https://{vault-name}.vault.azure.net/</code>.</p>

<p>You need to disable that check like so:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">vaultUri</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"https:://localhost:4997"</span><span class="p">);</span> <span class="c1">// or get it from your configuration, env vars etc.</span>

<span class="kt">var</span> <span class="n">options</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SecretClientOptions</span>
<span class="p">{</span>
    <span class="n">DisableChallengeResourceVerification</span> <span class="p">=</span> <span class="k">true</span>
<span class="p">};</span>

<span class="kt">var</span> <span class="n">secretClient</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SecretClient</span><span class="p">(</span><span class="n">vaultUri</span><span class="p">,</span> <span class="k">new</span> <span class="nf">DefaultAzureCredential</span><span class="p">(),</span> <span class="n">options</span><span class="p">);</span>

<span class="kt">var</span> <span class="n">secret</span> <span class="p">=</span> <span class="k">await</span> <span class="n">secretClient</span><span class="p">.</span><span class="nf">GetSecretAsync</span><span class="p">(</span><span class="s">"myPassword"</span><span class="p">);</span>
</code></pre></div></div>

<p>To make this even easier the <a href="https://www.nuget.org/packages/AzureKeyVaultEmulator.Client">AzureKeyVaultEmulator.Client</a> library is also available (netstandard2.0):</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Injected by Aspire using the name "keyvault".</span>
<span class="kt">var</span> <span class="n">vaultUri</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetConnectionString</span><span class="p">(</span><span class="s">"keyvault"</span><span class="p">)</span> <span class="p">??</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

<span class="c1">// Basic Secrets only implementation</span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddAzureKeyVaultEmulator</span><span class="p">(</span><span class="n">vaultUri</span><span class="p">);</span>
</code></pre></div></div>

<p>You can also optionally inject a <code class="language-plaintext highlighter-rouge">KeyClient</code> and <code class="language-plaintext highlighter-rouge">CertificateClient</code> like so:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Or configure which clients you need to use</span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddAzureKeyVaultEmulator</span><span class="p">(</span><span class="n">vaultUri</span><span class="p">,</span> <span class="n">secrets</span><span class="p">:</span> <span class="k">true</span><span class="p">,</span> <span class="n">keys</span><span class="p">:</span> <span class="k">true</span><span class="p">,</span> <span class="n">certificates</span><span class="p">:</span> <span class="k">false</span><span class="p">);</span>
</code></pre></div></div>

<p>Now you can create your application as normal, using the Azure Key Vault clients you injected at runtime:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="n">SecretClient</span> <span class="n">_secretClient</span><span class="p">;</span>

<span class="k">public</span> <span class="nf">SecretsController</span><span class="p">(</span><span class="n">SecretClient</span> <span class="n">secretClient</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">_secretClient</span> <span class="p">=</span> <span class="n">secretClient</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="nf">GetSecretValue</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">secret</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_secretClient</span><span class="p">.</span><span class="nf">GetSecretAsync</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>

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

<p>It’s <strong>highly</strong> recommended to check the execution environment at startup to prevent using the Azure Key Vault Emulator in production, for example if you’re using the <code class="language-plaintext highlighter-rouge">Client</code> library:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">vaultUri</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetConnectionString</span><span class="p">(</span><span class="s">"keyvault"</span><span class="p">)</span> <span class="p">??</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>

<span class="k">if</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="n">Environment</span><span class="p">.</span><span class="nf">IsDevelopment</span><span class="p">())</span>
    <span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddAzureKeyVaultEmulator</span><span class="p">(</span><span class="n">vaultUri</span><span class="p">,</span> <span class="n">secrets</span><span class="p">:</span> <span class="k">true</span><span class="p">,</span> <span class="n">certificates</span><span class="p">:</span> <span class="k">true</span><span class="p">,</span> <span class="n">keys</span><span class="p">:</span> <span class="k">true</span><span class="p">);</span>
<span class="k">else</span>
    <span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddAzureClients</span><span class="p">(</span><span class="n">client</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">asUri</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="n">vaultUri</span><span class="p">);</span>

        <span class="n">client</span><span class="p">.</span><span class="nf">AddSecretClient</span><span class="p">(</span><span class="n">asUri</span><span class="p">);</span>
        <span class="n">client</span><span class="p">.</span><span class="nf">AddKeyClient</span><span class="p">(</span><span class="n">asUri</span><span class="p">);</span>
        <span class="n">client</span><span class="p">.</span><span class="nf">AddCertificateClient</span><span class="p">(</span><span class="n">asUri</span><span class="p">);</span>
    <span class="p">});</span>
</code></pre></div></div>

<p>My PR into the main <code class="language-plaintext highlighter-rouge">Aspire.Azure.Security.Client</code> package which adds support for the <code class="language-plaintext highlighter-rouge">KeyClient</code> and <code class="language-plaintext highlighter-rouge">CertificateClient</code> is <a href="https://github.com/dotnet/aspire/pull/8408">almost through peer review</a> and expected to be in the .NET Aspire 9.3 release - until then you’ll need to use the usual Azure SDK For .NET if you need those clients.</p>

<h2 id="final-remarks">Final remarks</h2>

<p>I want to thank <a href="https://github.com/Basis-Theory/azure-keyvault-emulator">Basis Theory</a> for the original repository/codebase of which the Emulator then grew into its’ current form. When trying to find a suitable emulator myself I stumbled across it but was sad to see it only supported a few operations and was archived.</p>

<p>Prior to the stable release I got in touch with them to make sure that they were happy with the copyright retention and attribution back, but also to potentially add a redirect link from their archived repository to the now active one. They’ve been <em>incredibly</em> kind and communicative, so thank you to all of the team 💖.</p>

<p>Now, the future.</p>

<p>The Open Source .NET landscape has changed recently with largely adopted packages opting to move towards a commercial license for future updates, with prior releases keeping their OSS license but receiving no further support.</p>

<p>I fully back developers being paid for their work, and when large companies make money from their free labour it’s only fair that they get a piece of the pie too. However this makes introducing new open source dependencies into your workflow a little more risky, or at least more people are now aware of the risk.</p>

<p>That said, Azure Key Vault is not an ever changing product (and by proxy the Emulator); by design the functionality rarely changes to ensure that no security regressions creep in. The Emulator will continue to receive bug fixes, API updates (if available) and general maintenance work but it is <em>stable</em> and will not demand 1000s of hours of continued work. It will never be commercialised (I’d be sued into an early grave if I tried anyway) and with the <a href="https://github.com/dotnet-foundation/projects/issues/441">prospect of .NET Foundation membership on the horizon</a> my own personal bus factor decreases too.</p>

<p>If you make use of the Azure Key Vault Emulator please be sure to ⭐ the <a href="https://github.com/james-gould/azure-keyvault-emulator">repository</a> as it’s one of many metrics that the .NET Foundation uses to validate project membership applications.</p>

<p>Also number go up = happy ape brain.</p>

<p>Thanks for reading and I hope you enjoy the Emulator!</p>

<p>James</p>]]></content><author><name>James Gould</name><email>contact@jamesgould.dev</email></author><category term="csharp" /><category term="dotnet" /><category term="github" /><category term="dotnet aspire" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Using SSL Certificates in a .NET GitHub Workflow</title><link href="https://jamesgould.dev/posts/Use-SSL-certificates-In-Dotnet-Github-Workflow/" rel="alternate" type="text/html" title="Using SSL Certificates in a .NET GitHub Workflow" /><published>2025-03-25T00:00:00+00:00</published><updated>2025-03-25T00:00:00+00:00</updated><id>https://jamesgould.dev/posts/Use-SSL-certificates-In-Dotnet-Github-Workflow</id><content type="html" xml:base="https://jamesgould.dev/posts/Use-SSL-certificates-In-Dotnet-Github-Workflow/"><![CDATA[<p>I recently began working on the new implementation of the <a href="https://github.com/james-gould/azure-keyvault-emulator">Azure Keyvault Emulator</a> and created a heap of integration tests. As part of my build and release process I wanted to run something akin to:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet test --verbosity minimal
</code></pre></div></div>

<p>but due to the tests <em>requiring</em> SSL connections my tests were failing with:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   <span class="n">System</span><span class="p">.</span><span class="n">Net</span><span class="p">.</span><span class="n">Http</span><span class="p">.</span><span class="n">HttpRequestException</span> <span class="p">:</span> <span class="n">The</span> <span class="n">SSL</span> <span class="n">connection</span> <span class="n">could</span> <span class="k">not</span> <span class="n">be</span> <span class="n">established</span><span class="p">,</span> <span class="n">see</span> <span class="n">inner</span> <span class="n">exception</span><span class="p">.</span>
<span class="p">----</span> <span class="n">System</span><span class="p">.</span><span class="n">Security</span><span class="p">.</span><span class="n">Authentication</span><span class="p">.</span><span class="n">AuthenticationException</span> <span class="p">:</span> <span class="n">The</span> <span class="n">remote</span> <span class="n">certificate</span> <span class="k">is</span> <span class="n">invalid</span> <span class="n">because</span> <span class="n">of</span> <span class="n">errors</span> <span class="k">in</span> <span class="n">the</span> <span class="n">certificate</span> <span class="n">chain</span><span class="p">:</span> <span class="n">UntrustedRoot</span>
</code></pre></div></div>

<p>I tried a few solutions, including asking ChatGPT who dumped out a <em>load</em> of completely unworking code, but the following Just Worked™:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build-and-push</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout Repository</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup .NET</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-dotnet@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">dotnet-version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">9.0.x'</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install SSL Certificates</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">dotnet dev-certs https --trust</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run Integration Tests</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">dotnet test --verbosity minimal</span>
</code></pre></div></div>

<p>Super simple, you just need to:</p>

<ul>
  <li>Set up the .NET action <code class="language-plaintext highlighter-rouge">uses: actions/setup-dotnet@v3</code></li>
  <li>Use the <code class="language-plaintext highlighter-rouge">dev-certs</code> CLI tool to create and trust an SSL cert: <code class="language-plaintext highlighter-rouge">dotnet dev-certs https --trust</code></li>
  <li>Then run your tests <code class="language-plaintext highlighter-rouge">dotnet test --verbosity minimal</code></li>
</ul>]]></content><author><name>James Gould</name><email>contact@jamesgould.dev</email></author><category term="csharp" /><category term="dotnet" /><category term="github" /><summary type="html"><![CDATA[I recently began working on the new implementation of the Azure Keyvault Emulator and created a heap of integration tests. As part of my build and release process I wanted to run something akin to:]]></summary></entry><entry><title type="html">Syntax Highlighting C# on a Jekyll Blog</title><link href="https://jamesgould.dev/posts/jekyll-csharp-syntax-highlighting/" rel="alternate" type="text/html" title="Syntax Highlighting C# on a Jekyll Blog" /><published>2025-03-17T00:00:00+00:00</published><updated>2025-03-17T00:00:00+00:00</updated><id>https://jamesgould.dev/posts/jekyll-csharp-syntax-highlighting</id><content type="html" xml:base="https://jamesgould.dev/posts/jekyll-csharp-syntax-highlighting/"><![CDATA[<p>Syntax highlighting is hard but luckily Jekyll makes use of Liquid Tags and Rouge to simplify it for you.</p>

<p>One of the cool things is I can write pure markdown for these blog posts, append a language option to the triple backticks and set the highlighting:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>```csharp
// Code here
```
</code></pre></div></div>

<p>The only issue is that the defaults look terrible:</p>

<p><img src="https://i.imgur.com/bGEDMur.png" alt="default jekyll syntax highlighting for C#" /></p>

<p>So let’s fix that.</p>

<h1 id="other-solutions">Other solutions</h1>

<p>I tried a whole heap of solutions to get this working in a nice way, including but not limited to:</p>

<ul>
  <li>Writing my own Ruby plugin to render differently</li>
  <li>Manually tweaking the <code class="language-plaintext highlighter-rouge">syntax.css</code> file I use for highlighting</li>
  <li>Installing <em>bloody Rust</em> to try and make use of <a href="https://tree-sitter.github.io/tree-sitter/">kramdown Tree Sitter</a></li>
  <li>Giving up and going to bed.</li>
</ul>

<p>None of these actually came out looking anything close to what Visual Studio shows you, which is mostly a limitation of the <a href="https://github.com/rouge-ruby/rouge/blob/master/lib/rouge/lexers/csharp.rb">Rouge CSharp lexer</a>.</p>

<p>I don’t, and won’t, complain about people providing solutions to problems for free in the open source world. I can’t thank people <em>enough</em> for doing it because the modern internet wouldn’t be the same without it.</p>

<p>That said, it didn’t work for me. Time to fix it.</p>

<h1 id="the-final-solution">The final solution</h1>

<p>I relented and asked ChatGPT what I should do in this situation who presented The Chosen One™:</p>

<p>We’re going to be making use of <a href="">Prism.js</a> instead, which looks significantly better. In a common <code class="language-plaintext highlighter-rouge">.html</code> file for your blog include the following:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>

<span class="c">&lt;!--language support--&gt;</span>
<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-csharp.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div></div>

<p>Next find a good theme that you like <a href="https://github.com/PrismJS/prism-themes?tab=readme-ov-file#available-themes">here</a>. I personally chose <a href="https://github.com/PrismJS/prism-themes/blob/master/themes/prism-vsc-dark-plus.css">VS Code Dark</a>.</p>

<p>Download that css and place it into a <code class="language-plaintext highlighter-rouge">.css</code> file inside your <code class="language-plaintext highlighter-rouge">assets/css</code> folder. Reference the new <code class="language-plaintext highlighter-rouge">css</code> file in your common file above like so:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"/assets/css/prismjs-vs.css"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>And finally, in a file exclusively used by your <code class="language-plaintext highlighter-rouge">posts</code>, add this to the bottom of the file <strong>before</strong> the <code class="language-plaintext highlighter-rouge">&lt;/html&gt;</code> tag:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="o">&lt;</span><span class="nx">script</span><span class="o">&gt;</span>
    <span class="nb">document</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">DOMContentLoaded</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
        <span class="nx">Prism</span><span class="p">.</span><span class="nf">highlightAll</span><span class="p">();</span>
    <span class="p">});</span>
  <span class="o">&lt;</span><span class="sr">/script</span><span class="err">&gt;
</span></code></pre></div></div>

<p>and there we go! Here’s the newly highlighted code above, but looking as close as I could possibly get it to Visual Studio:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="nn">AzureKeyVaultEmulator.Shared.Constants</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">AspireConstants</span>
    <span class="p">{</span>
        <span class="k">public</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">EmulatorServiceName</span> <span class="p">=</span> <span class="s">"keyVaultEmulatorApi"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="nn">AzureKeyVaultEmulator.IntegrationTests.SetupHelper</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">ExampleReference</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">referencedString</span> <span class="p">=</span> <span class="n">AspireConstants</span><span class="p">.</span><span class="n">EmulatorServiceName</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>and another example of a method block with various keywords:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">TestingFixture</span> <span class="p">:</span> <span class="n">IAsyncLifetime</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">DistributedApplication</span><span class="p">?</span> <span class="n">_app</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">ResourceNotificationService</span><span class="p">?</span> <span class="n">_notificationService</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">InitializeAsync</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="k">await</span> <span class="n">DistributedApplicationTestingBuilder</span><span class="p">.</span><span class="n">CreateAsync</span><span class="p">&lt;</span><span class="n">Projects</span><span class="p">.</span><span class="n">AzureKeyVaultEmulator_AppHost</span><span class="p">&gt;();</span>

        <span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">ConfigureHttpClientDefaults</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span>
        <span class="p">{</span>
            <span class="n">c</span><span class="p">.</span><span class="nf">AddStandardResilienceHandler</span><span class="p">();</span>
        <span class="p">});</span>

        <span class="n">_app</span> <span class="p">=</span> <span class="k">await</span> <span class="n">builder</span><span class="p">.</span><span class="nf">BuildAsync</span><span class="p">();</span>

        <span class="n">_notificationService</span> <span class="p">=</span> <span class="n">_app</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">GetService</span><span class="p">&lt;</span><span class="n">ResourceNotificationService</span><span class="p">&gt;();</span>

        <span class="k">await</span> <span class="n">_app</span><span class="p">.</span><span class="nf">StartAsync</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">DisposeAsync</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_app</span> <span class="k">is</span> <span class="k">not</span> <span class="k">null</span><span class="p">)</span>
            <span class="k">await</span> <span class="n">_app</span><span class="p">.</span><span class="nf">DisposeAsync</span><span class="p">().</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>James Gould</name><email>contact@jamesgould.dev</email></author><category term="csharp" /><category term="jekyll" /><summary type="html"><![CDATA[Syntax highlighting is hard but luckily Jekyll makes use of Liquid Tags and Rouge to simplify it for you.]]></summary></entry><entry><title type="html">Integration Testing Quick Start with .NET Aspire</title><link href="https://jamesgould.dev/posts/NET-Aspire-Integration-Testing-Troubleshooting/" rel="alternate" type="text/html" title="Integration Testing Quick Start with .NET Aspire" /><published>2025-03-16T00:00:00+00:00</published><updated>2025-03-16T00:00:00+00:00</updated><id>https://jamesgould.dev/posts/NET-Aspire-Integration-Testing-Troubleshooting</id><content type="html" xml:base="https://jamesgould.dev/posts/NET-Aspire-Integration-Testing-Troubleshooting/"><![CDATA[<h1 id="introduction">Introduction</h1>

<p>One of my main gripes with Microsoft is their documentation. If you’re starting something from scratch you’ll naturally Google around for it, find the docs and the example code looks super simple; then you get into the nitty gritty and suddenly you’re riding solo.</p>

<p>I’ve decided to fork the <a href="https://github.com/james-gould/azure-keyvault-emulator">Azure KeyVault Emulator</a>, which has been archived as of October 2024, because the functionality is really quite desirable. Aspire has allowed us to create local cloud environments at dev-time without needing to actually host or deploy resources, nor configure RBAC access (shudders). Key Vault is one of those services that <em>doesn’t</em> have support in Aspire (yet?) and you need to have a real instance of it hosted on Azure. With the emulator, and my future development/extensions, this won’t be required.</p>

<p>I wanted to create integration tests for the API to ensure it met the acceptance criteria for public consumption - naturally I googled “.NET Aspire Integration Testing” and <a href="https://learn.microsoft.com/en-us/dotnet/aspire/testing/write-your-first-test?pivots=xunit">this was the first link I saw</a>, from Microsoft themselves!</p>

<p>I use XUnit for all my testing purposes (mainly from habit) and .NET Aspire has a handy XUnit template you can just create a project from and now you have integration testing support!</p>

<p>Right? No. Why else would I be writing this?</p>

<h1 id="setting-up-correctly">Setting up correctly</h1>

<p>We’ll be creating a new <code class="language-plaintext highlighter-rouge">.NET Aspire XUnit Project</code> from Visual Studio:</p>

<p><img src="https://i.imgur.com/gKXsc5l.png" alt=".NET Aspire Integration Testing Project using XUnit" /></p>

<p>Next step is to add a reference to your <code class="language-plaintext highlighter-rouge">AppHost</code> project in your new <code class="language-plaintext highlighter-rouge">XUnit</code> project:</p>

<p><img src="https://i.imgur.com/PWT3Iby.png" alt=".Net Aspire Integration Testing AppHost Setup" /></p>

<p>This will allow your new <code class="language-plaintext highlighter-rouge">IntegrationTesting</code> project to run the <code class="language-plaintext highlighter-rouge">AppHost</code> project, which in turn creates all the required resources, connection strings and so forth. Really bloody handy.</p>

<h1 id="sharedreference-projects">Shared/reference projects</h1>

<p>One of the nuances of <code class="language-plaintext highlighter-rouge">.NET Aspire</code> is any projects referenced are classed as <code class="language-plaintext highlighter-rouge">executables</code> - meaning if you need a <code class="language-plaintext highlighter-rouge">Model</code> from your API to create an integration test from it cannot exist in the same project as your API.</p>

<p>Annoying, but not a hill worth dying on. I migrated the models out of the <code class="language-plaintext highlighter-rouge">API</code> project and into a <code class="language-plaintext highlighter-rouge">Shared</code> project, and referenced it as such:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ProjectReference</span> <span class="na">Include=</span><span class="s">"..\AzureKeyVaultEmulator.Shared\AzureKeyVaultEmulator.Shared.csproj"</span> <span class="na">IsAspireProjectResource=</span><span class="s">"false"</span><span class="nt">/&gt;</span>
</code></pre></div></div>

<p>The really key part here is the <code class="language-plaintext highlighter-rouge">IsAspireProjectResource="false"</code> which allows you to reference items from within that project like you would a normal project reference. For example:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="nn">AzureKeyVaultEmulator.Shared.Constants</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">AspireConstants</span>
    <span class="p">{</span>
        <span class="k">public</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">EmulatorServiceName</span> <span class="p">=</span> <span class="s">"keyVaultEmulatorApi"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="nn">AzureKeyVaultEmulator.IntegrationTests.SetupHelper</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">ExampleReference</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">referencedString</span> <span class="p">=</span> <span class="n">AspireConstants</span><span class="p">.</span><span class="n">EmulatorServiceName</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I personally recommend encapsulating any names for applications into a <code class="language-plaintext highlighter-rouge">const string</code> like above because it allows for cleaner code and reusability.</p>

<p>…or I’m just lazy and can’t be bothered to update 3 strings when I make a change.</p>

<h1 id="create-your-testing-environment">Create your testing environment</h1>

<p>Now you’re finally ready to create your testing environment! Let’s get cracking.</p>

<p>First off we’re going to need a <code class="language-plaintext highlighter-rouge">Fixture</code>, which encapsulates the environment for your actual test cases. This fixture will make use of the reference to your <code class="language-plaintext highlighter-rouge">AppHost</code>, creating an instance of it per test class, and then using its’ exposed endpoints/services to execute tests.</p>

<p>You don’t <em>strictly</em> need to do this, but you really should. If you’re writing more than a single integration test then you’ll save a load of time writing/copying the same code per test case, and any faults that crop up can be fixed in a single area.</p>

<p>Again, reusability! It’s important - and this is why the MS documentation kind of sucks sometimes.</p>

<p>Create a basic, minimal <code class="language-plaintext highlighter-rouge">Fixture</code> inside of your <code class="language-plaintext highlighter-rouge">IntegrationTesting</code> project (ideally in a suitably named folder):</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">TestingFixture</span> <span class="p">:</span> <span class="n">IAsyncLifetime</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">DistributedApplication</span><span class="p">?</span> <span class="n">_app</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">ResourceNotificationService</span><span class="p">?</span> <span class="n">_notificationService</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">InitializeAsync</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="k">await</span> <span class="n">DistributedApplicationTestingBuilder</span><span class="p">.</span><span class="n">CreateAsync</span><span class="p">&lt;</span><span class="n">Projects</span><span class="p">.</span><span class="n">AzureKeyVaultEmulator_AppHost</span><span class="p">&gt;();</span>

        <span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">ConfigureHttpClientDefaults</span><span class="p">(</span><span class="n">c</span> <span class="p">=&gt;</span>
        <span class="p">{</span>
            <span class="n">c</span><span class="p">.</span><span class="nf">AddStandardResilienceHandler</span><span class="p">();</span>
        <span class="p">});</span>

        <span class="n">_app</span> <span class="p">=</span> <span class="k">await</span> <span class="n">builder</span><span class="p">.</span><span class="nf">BuildAsync</span><span class="p">();</span>

        <span class="n">_notificationService</span> <span class="p">=</span> <span class="n">_app</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">GetService</span><span class="p">&lt;</span><span class="n">ResourceNotificationService</span><span class="p">&gt;();</span>

        <span class="k">await</span> <span class="n">_app</span><span class="p">.</span><span class="nf">StartAsync</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">DisposeAsync</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_app</span> <span class="k">is</span> <span class="k">not</span> <span class="k">null</span><span class="p">)</span>
            <span class="k">await</span> <span class="n">_app</span><span class="p">.</span><span class="nf">DisposeAsync</span><span class="p">().</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s go over the important bits here:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">InitialiseAsync()</code> creates our <code class="language-plaintext highlighter-rouge">DistrubutedApplication</code> - ie the <code class="language-plaintext highlighter-rouge">AppHost</code>. We want to keep a higher scoped reference to that for the future: <code class="language-plaintext highlighter-rouge">_app</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">ResourceNotificationService</code> allows us to <code class="language-plaintext highlighter-rouge">WaitAsync()</code> for resources that <code class="language-plaintext highlighter-rouge">.NET Aspire</code> is creating before trying to use them; essentially the testing version of <code class="language-plaintext highlighter-rouge">WaitFor(xxx)</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">DisposeAsync()</code> cleans up the <code class="language-plaintext highlighter-rouge">DistributedApplication</code> on a per-class basis. Without this you will have lingering resources/ports should your tests throw an <code class="language-plaintext highlighter-rouge">Exception</code> and bottom out.</li>
</ul>

<p>We’re trying to test an API, so we need a <code class="language-plaintext highlighter-rouge">HttpClient</code> which points to our <code class="language-plaintext highlighter-rouge">localhost:XXXX</code> resource.</p>

<p>Unless we hardcode the <code class="language-plaintext highlighter-rouge">port</code> we need to look this up. The Microsoft docs tell you to create these on a per-test basis, gah, which is not ideal.</p>

<p>Let’s create, and expose, a <code class="language-plaintext highlighter-rouge">HttpClient</code> from our <code class="language-plaintext highlighter-rouge">Fixture</code> by extending its’ functionality:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="n">HttpClient</span><span class="p">?</span> <span class="n">_testingClient</span><span class="p">;</span> <span class="c1">// Placed below _notificationService</span>

<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpClient</span><span class="p">&gt;</span> <span class="nf">CreateHttpClient</span><span class="p">(</span><span class="kt">string</span> <span class="n">applicationName</span> <span class="p">=</span> <span class="n">AspireConstants</span><span class="p">.</span><span class="n">EmulatorServiceName</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_testingClient</span> <span class="k">is</span> <span class="k">not</span> <span class="k">null</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">_testingClient</span><span class="p">;</span>

    <span class="n">_testingClient</span> <span class="p">=</span> <span class="n">_app</span><span class="p">!.</span><span class="nf">CreateHttpClient</span><span class="p">(</span><span class="n">applicationName</span><span class="p">);</span>

    <span class="k">await</span> <span class="n">_notificationService</span><span class="p">!.</span><span class="nf">WaitForResourceAsync</span><span class="p">(</span><span class="n">applicationName</span><span class="p">,</span> <span class="n">KnownResourceStates</span><span class="p">.</span><span class="n">Running</span><span class="p">).</span><span class="nf">WaitAsync</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">30</span><span class="p">));</span>

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

<p>We’re now using the <code class="language-plaintext highlighter-rouge">CreateHttpClient("MyApiProject")</code> method which under the hood uses <code class="language-plaintext highlighter-rouge">IHttpClientFactory</code> <a href="https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting.Testing/DistributedApplicationHostingTestingExtensions.cs#L23-L34">as you can see here</a>. No need to worry about hardcoding the IP and Port for your application, delegate the work to <code class="language-plaintext highlighter-rouge">.NET Aspire</code>.</p>

<p>Next we’re making use of our <code class="language-plaintext highlighter-rouge">ResourceNotificationService</code> to wait for the API to be alive. This is near-instant once the <code class="language-plaintext highlighter-rouge">AppHost</code> has launched but extremely useful if you have a slow start-up (such as waiting for a database server).</p>

<p>We’re also making use of your <code class="language-plaintext highlighter-rouge">AspireConstants.EmulatorServiceName</code> here to keep our code clean too! I will keep beating the reusability drum.</p>

<h1 id="create-your-integration-tests">Create your integration test(s)</h1>

<p>Okay, finally, we can start writing tests in a way that is easy to maintain.</p>

<p>First off create a new <code class="language-plaintext highlighter-rouge">TestClass</code> for a particular <code class="language-plaintext highlighter-rouge">Controller</code>, <code class="language-plaintext highlighter-rouge">Endpoint</code>, whatever you want to test:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">GetSecretTests</span><span class="p">(</span><span class="n">TestingFixture</span> <span class="n">fixture</span><span class="p">)</span> <span class="p">:</span> <span class="n">IClassFixture</span><span class="p">&lt;</span><span class="n">TestingFixture</span><span class="p">&gt;</span>
<span class="p">{</span>

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

<p>The <code class="language-plaintext highlighter-rouge">IClassFixture&lt;TestingFixture&gt;</code> tells our class that we’re a testing class specifically, and exposes the <code class="language-plaintext highlighter-rouge">TestingFixture</code> to it.</p>

<p>We’re using the new <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/primary-constructors">Primary Constructor</a> syntax - which my eyes/brain still haven’t gotten used to.</p>

<p>Now let’s add a test and call into our API:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Fact</span><span class="p">]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">GetSecretsBlocksRequestWithoutBearerTokenTest</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">await</span> <span class="n">fixture</span><span class="p">.</span><span class="nf">CreateHttpClient</span><span class="p">();</span>

    <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span><span class="s">"secrets/willfail"</span><span class="p">);</span>

    <span class="n">Assert</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">Unauthorized</span><span class="p">,</span> <span class="n">response</span><span class="p">.</span><span class="n">StatusCode</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As you can see the <code class="language-plaintext highlighter-rouge">TestingFixture</code> allows us to create a bog-standard <code class="language-plaintext highlighter-rouge">HttpClient</code> which we can use to interact with our API - which has been hosted by <code class="language-plaintext highlighter-rouge">.NET Aspire</code>.</p>

<p>Remember the point I made about migrating API models to a <code class="language-plaintext highlighter-rouge">Shared</code> project earlier? If you need to go further into validating the response, ie checking a field has been set correctly, you can do so like you would consuming a 3rd party API:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Fact</span><span class="p">]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">GetSecretsReturnsBackCorrectValueTest</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">await</span> <span class="n">fixture</span><span class="p">.</span><span class="nf">CreateHttpClient</span><span class="p">();</span>

    <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span><span class="s">"secrets/myPassword"</span><span class="p">);</span>

    <span class="kt">var</span> <span class="n">secret</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="n">ReadFromJsonAsync</span><span class="p">&lt;</span><span class="n">SecretResponse</span><span class="p">&gt;();</span>

    <span class="n">Assert</span><span class="p">.</span><span class="nf">NotEqual</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">,</span> <span class="n">secret</span><span class="p">?.</span><span class="n">Value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And that’s it! Now you have a fully reusable <code class="language-plaintext highlighter-rouge">Fixture</code> and a set up integration testing environment for your API, along with all resources required by your platform to operate.</p>

<h1 id="final-notes">Final notes</h1>

<p>If you’re using a versioned API and don’t want to repeat yourself endlessly (reusabili- okay I’ll stop) you can modify the <code class="language-plaintext highlighter-rouge">CreateHttpClient</code> method in your <code class="language-plaintext highlighter-rouge">Fixture</code> to do that for you. I’m using the following:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Asp.Versioning.Http"</span> <span class="na">Version=</span><span class="s">"8.1.0"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Asp.Versioning.Http.Client"</span> <span class="na">Version=</span><span class="s">"8.1.0"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>And implementing them like so:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="n">HttpClient</span><span class="p">?</span> <span class="n">_testingClient</span><span class="p">;</span>

<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpClient</span><span class="p">&gt;</span> <span class="nf">CreateHttpClient</span><span class="p">(</span><span class="kt">double</span> <span class="n">version</span><span class="p">,</span> <span class="kt">string</span> <span class="n">applicationName</span> <span class="p">=</span> <span class="n">AspireConstants</span><span class="p">.</span><span class="n">EmulatorServiceName</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_testingClient</span> <span class="k">is</span> <span class="k">not</span> <span class="k">null</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">_testingClient</span><span class="p">;</span>

    <span class="kt">var</span> <span class="n">opt</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ApiVersionHandler</span><span class="p">(</span><span class="k">new</span> <span class="nf">QueryStringApiVersionWriter</span><span class="p">(),</span> <span class="k">new</span> <span class="nf">ApiVersion</span><span class="p">(</span><span class="n">version</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">InnerHandler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClientHandler</span><span class="p">()</span> <span class="c1">// Make sure you add this!</span>
    <span class="p">};</span>

    <span class="kt">var</span> <span class="n">endpoint</span> <span class="p">=</span> <span class="n">_app</span><span class="p">!.</span><span class="nf">GetEndpoint</span><span class="p">(</span><span class="n">applicationName</span><span class="p">);</span>

    <span class="n">_testingClient</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">(</span><span class="n">opt</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">BaseAddress</span> <span class="p">=</span> <span class="n">endpoint</span>
    <span class="p">};</span>

    <span class="k">await</span> <span class="n">_notificationService</span><span class="p">!.</span><span class="nf">WaitForResourceAsync</span><span class="p">(</span><span class="n">applicationName</span><span class="p">,</span> <span class="n">KnownResourceStates</span><span class="p">.</span><span class="n">Running</span><span class="p">).</span><span class="nf">WaitAsync</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">30</span><span class="p">));</span>

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

<p>And then configure your tests to alter the API version like so:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Theory</span><span class="p">]</span>
<span class="p">[</span><span class="nf">InlineData</span><span class="p">(</span><span class="m">1.0</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">InlineData</span><span class="p">(</span><span class="m">1.1</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">GetSecretsReturnsBackCorrectValueTest</span><span class="p">(</span><span class="kt">double</span> <span class="n">version</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">await</span> <span class="n">fixture</span><span class="p">.</span><span class="nf">CreateHttpClient</span><span class="p">(</span><span class="n">version</span><span class="p">);</span> <span class="c1">// Included here!</span>

    <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span><span class="s">"secrets/myPassword"</span><span class="p">);</span>

    <span class="kt">var</span> <span class="n">secret</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="n">ReadFromJsonAsync</span><span class="p">&lt;</span><span class="n">SecretResponse</span><span class="p">&gt;();</span>

    <span class="n">Assert</span><span class="p">.</span><span class="nf">NotEqual</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">,</span> <span class="n">secret</span><span class="p">?.</span><span class="n">Value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This implementation will append <code class="language-plaintext highlighter-rouge">api-version={version}</code> to the end ouf our request, where <code class="language-plaintext highlighter-rouge">version</code> is provided by <code class="language-plaintext highlighter-rouge">[InlineData]</code>.</p>

<p>You can alter where that version goes, I was going to link the documentation as a tongue-in-cheek joke but in classic Microsoft fashion this happened:</p>

<p><img src="https://i.imgur.com/0OeGTiM.png" alt="Documentation missing from MS" /></p>

<p>Honestly couldn’t make it up.</p>

<p>Thanks for reading!</p>]]></content><author><name>James Gould</name><email>contact@jamesgould.dev</email></author><category term="dotnet 8" /><category term="dotnet Aspire" /><category term="integration testing" /><category term="csharp" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Fast Static Jekyll Blog Deployment with Azure CI/CD</title><link href="https://jamesgould.dev/posts/easy-azure-static-webapp-blog/" rel="alternate" type="text/html" title="Fast Static Jekyll Blog Deployment with Azure CI/CD" /><published>2022-02-04T00:00:00+00:00</published><updated>2022-02-04T00:00:00+00:00</updated><id>https://jamesgould.dev/posts/easy-azure-static-webapp-blog</id><content type="html" xml:base="https://jamesgould.dev/posts/easy-azure-static-webapp-blog/"><![CDATA[<h1 id="introduction">Introduction</h1>

<p>A few days prior, when I set this blog up, I wanted to shortcut a lot of the legwork with setting up a proper CI/CD pipeline to build and release out to an Azure Static Web App.</p>

<p>If you’re a fan of horror stories feel free to read my <a href="https://jamesgould.dev/posts/sinning-via-azure-devops/">initial attempt here</a>.</p>

<p>Some future requirements have come up which meant that I not only needed a proper <em>build</em> pipeline, but also a fast one.</p>

<p>For those unaware, Azure Devops has a free tier which includes 1800 minutes of build time per month, refreshing on the 1st of the month.</p>

<p>I’ve now built a true, fairly fast CI/CD pipeline to build and release this blog out and I’d like to share my findings with you, dear reader.</p>

<p>In case you just want the <code class="language-plaintext highlighter-rouge">YAML</code> and nothing else, here it is:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">trigger</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">master</span>

<span class="na">pool</span><span class="pi">:</span>
  <span class="na">vmImage</span><span class="pi">:</span> <span class="s1">'</span><span class="s">ubuntu-latest'</span>

<span class="na">schedules</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s2">"</span><span class="s">5</span><span class="nv"> </span><span class="s">0</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*"</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Daily release for scheduled posts</span>
  <span class="na">branches</span><span class="pi">:</span>
    <span class="na">include</span><span class="pi">:</span> 
     <span class="pi">-</span> <span class="s">master</span>
  <span class="na">always</span><span class="pi">:</span> <span class="kc">true</span> 

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">UseRubyVersion@0</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">versionSpec</span><span class="pi">:</span> <span class="s1">'</span><span class="s">&gt;=</span><span class="nv"> </span><span class="s">2.5'</span>

<span class="pi">-</span> <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">gem install jekyll bundler --no-rdoc</span>
    <span class="s">bundle install --retry=3 --jobs=4</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s1">'</span><span class="s">bundle</span><span class="nv"> </span><span class="s">install'</span>

<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">CmdLine@2</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">script</span><span class="pi">:</span> <span class="s">bundle exec jekyll build</span>
    <span class="na">displayName</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Jekyll</span><span class="nv"> </span><span class="s">Build'</span>
    
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">AzureStaticWebApp@0</span>
  <span class="na">inputs</span><span class="pi">:</span>
      <span class="na">app_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">/_site'</span>
      <span class="na">api_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">api'</span>
      <span class="na">output_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">/_site'</span>
      <span class="na">azure_static_web_apps_api_token</span><span class="pi">:</span> <span class="s">$(deployment_token)</span>

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

<h1 id="lets-break-it-down">Let’s break it down</h1>

<p>Let’s jump into the nitty-gritty of what this build config is doing, and why.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">schedules</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s2">"</span><span class="s">5</span><span class="nv"> </span><span class="s">0</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*"</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Daily release for scheduled posts</span>
  <span class="na">branches</span><span class="pi">:</span>
    <span class="na">include</span><span class="pi">:</span> 
     <span class="pi">-</span> <span class="s">master</span>
  <span class="na">always</span><span class="pi">:</span> <span class="kc">true</span> 
</code></pre></div></div>

<p>This is an optional include that will build the blog on a schedule, with the cron being every day at 00:05am. This is so I can schedule posts into the <code class="language-plaintext highlighter-rouge">all_collections/_posts</code> directory and have them release on the date of the post.</p>

<p>If you don’t want to schedule a build and simply want to trigger the release when you publish a new post, delete this section.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">UseRubyVersion@0</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">versionSpec</span><span class="pi">:</span> <span class="s1">'</span><span class="s">&gt;=</span><span class="nv"> </span><span class="s">2.5'</span>

<span class="pi">-</span> <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">gem install jekyll bundler --no-rdoc</span>
    <span class="s">bundle install --retry=3 --jobs=4</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s1">'</span><span class="s">bundle</span><span class="nv"> </span><span class="s">install'</span>
</code></pre></div></div>

<p>This is a 2 step part which has been the majority of the time sink I’ve experienced.</p>

<p>First we’re specifying which version of <code class="language-plaintext highlighter-rouge">Ruby</code> to use, as <a href="https://jekyllrb.com/docs/">Jekyll advises against using spec 3.0 or higher in their quickstart guide.</a></p>

<p>Next we’re pipelining two scripts to set the stage for our blog:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">gem install jekyll bundler --no-rdoc</code> which will allow the actual build script used by <code class="language-plaintext highlighter-rouge">Jekyll</code> to run correctly.</li>
  <li><code class="language-plaintext highlighter-rouge">bundle install --retry=3 --jobs=4</code> which will then install our dependencies etc, ready for compiling the blog markdown into HTML.</li>
</ul>

<p>Let’s take a deeper look into each.</p>

<h1 id="lightening-fast">Lightening fast?</h1>

<p><code class="language-plaintext highlighter-rouge">gem install jekyll bundler --no-rdoc</code> comprises of two parts, the command to run and the parameters.</p>

<p>The initital command <code class="language-plaintext highlighter-rouge">gem install jekyll bundler</code> typically takes around 2m30s to run on the Azure Free tier. Part of this, for whatever reason, is building documentation for the script which we, as “set-and-forget” pipeline runners, don’t care about. That’s where <code class="language-plaintext highlighter-rouge">--no-rdoc</code> comes in.</p>

<p>With this addition we skip the step where the documentation is created. This shaved around 30s off the total build time, saving our precious free minutes!</p>

<p><code class="language-plaintext highlighter-rouge">bundle install --retry=3 --jobs=4</code> is an important part of the process as building the Jekyll site requires the bundle to be installed. Some caching can be done here to mitigate further time, but I haven’t gotten to that yet.</p>

<p>The <code class="language-plaintext highlighter-rouge">--job=4</code> is the key part here. Without it, we run the process on a single core. By specifying <code class="language-plaintext highlighter-rouge">4</code> we’re able to run our command on 4 cores, shaving off a bunch of time. I didn’t really notice this before, but I took it out for a trial run and it added an extra couple of minutes to the build time.</p>

<h1 id="the-boring-stuff">The boring stuff</h1>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">CmdLine@2</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">script</span><span class="pi">:</span> <span class="s">bundle exec jekyll build</span>
    <span class="na">displayName</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Jekyll</span><span class="nv"> </span><span class="s">Build'</span>
</code></pre></div></div>

<p>Now that we’ve gotten our ducks firmly in a row it’s time to build out <code class="language-plaintext highlighter-rouge">xxx.md</code> files into actual HTML. This is a simple command-line script which runs <code class="language-plaintext highlighter-rouge">bundle exec jekyll build</code>. Only takes about 1s, no point trying to optimise it.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">AzureStaticWebApp@0</span>
  <span class="na">inputs</span><span class="pi">:</span>
      <span class="na">app_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">/_site'</span>
      <span class="na">api_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">api'</span>
      <span class="na">output_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">/_site'</span>
      <span class="na">azure_static_web_apps_api_token</span><span class="pi">:</span> <span class="s">$(deployment_token)</span>
</code></pre></div></div>

<p>At this point we’ve done all the required steps and we’re ready to publish out to our Azure Static Web App. We have a <code class="language-plaintext highlighter-rouge">pipeline variable</code> set called <code class="language-plaintext highlighter-rouge">deployment_token</code> which you can grab from the configuration blade of your Static Web App (detailed explanation on the previous post linked above).</p>

<p>This step takes the ready <code class="language-plaintext highlighter-rouge">_site</code> directory and publishes it out.</p>

<h2 id="aaaaand-breathe">Aaaaand breathe.</h2>

<p>Cool, we’re all set. It’s running, total time is typically just over 3 minutes which means we can run a whole bunch of these each day at <code class="language-plaintext highlighter-rouge">00:05</code> without stressing.</p>]]></content><author><name>James Gould</name><email>contact@jamesgould.dev</email></author><category term="jekyll" /><category term="azure devops" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Publishing Static Web App Blog via Azure Devops</title><link href="https://jamesgould.dev/posts/sinning-via-azure-devops/" rel="alternate" type="text/html" title="Publishing Static Web App Blog via Azure Devops" /><published>2022-01-28T00:00:00+00:00</published><updated>2022-01-28T00:00:00+00:00</updated><id>https://jamesgould.dev/posts/sinning-via-azure-devops</id><content type="html" xml:base="https://jamesgould.dev/posts/sinning-via-azure-devops/"><![CDATA[<h1 id="introduction">Introduction</h1>

<p>When deciding to build a blog there were a few options:</p>

<ul>
  <li>A custom domain name. Medium gets on my nerves and I wouldn’t want to inflict that on somebody else.</li>
  <li>Easy mechanism to design and publish posts, markdown being the optimal choice.</li>
  <li>A theme that was minimalistic but flexible.</li>
</ul>

<p>Initially I settled on <a href="https://hashnode.com/">Hashnode</a> which allowed easy setup, 1-click themes and a free custom domain name alias.</p>

<p>After setting it up it was swiftly apparent how bloated the platform was. I just wanted somewhere to write up interesting topics, not participate in some global developer community where we all pass-the-upvote and #followback.</p>

<p>With limited knowledge in the space I stumbled across Jekyll, an open source markdown-driven platform which generates static HTML from markdown files with an associated theme. Perfect!</p>

<p>I got my site set up locally, got the theme installed (took some time) and got the build working so that updates I published would actually generate into the HTML.</p>

<p>Next step: deployment.</p>

<p>I’m no guru with CI/CD. I can pick apart pre-assembled YAML files and make them work, or follow a wizard to generate the tasks behind the scenes. If there’s a bug, I bin the lot off and start again. I just needed to find a way to build and publish the site to some web server every time I pushed a new post to the repository the blog sat on. Should be easy enough.</p>

<h1 id="pragmatism">Pragmatism</h1>

<p>I settled on an Azure Static Web App. It offers a generous free tier with an automatic SSL certificate, free domain mapping and I could easily deploy my static content manually via Kudu. I wanted to automate this step because I’m a lazy bugger, so I gave it a shot.</p>

<p>Frankly I won’t waste your time at this stage with waffling on about my attempts to get various ruby scripts running, with artifacts being shoved around etc. There are guides out there if you’re interested - I couldn’t make heads nor tails of them.</p>

<p>Here’s what I knew worked:</p>

<ul>
  <li>Locally the blog compiled and looked fine once running with <code class="language-plaintext highlighter-rouge">bundle exec jeykll serve</code>.</li>
  <li>The <code class="language-plaintext highlighter-rouge">_site</code> directory compiled fully, taking new additions to the <code class="language-plaintext highlighter-rouge">all_collections/_posts</code> directory and building them into a HTML file.</li>
</ul>

<p>Now, cue the super scuffed approach to deploying this blog.</p>

<h1 id="rules-are-meant-to-be-completely-deleted-from-the-file">Rules are meant to be completely deleted from the file</h1>

<p>For this <em>pragmatic</em> guide we need to commit our first crime: remove the exclusion of <code class="language-plaintext highlighter-rouge">_site</code> from the <code class="language-plaintext highlighter-rouge">.gitignore</code>. How dreadful!</p>

<h1 id="forgive-me-microsoft-for-i-have-sinned">Forgive me Microsoft for I have sinned.</h1>

<p>Here’s the dreadful YAML file to use for publishing a single post. It’s not pretty, but it works. All in the name of getting it working without too much glitter:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">trigger</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">master</span>

<span class="na">pool</span><span class="pi">:</span>
  <span class="na">vmImage</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

<span class="na">steps</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">checkout</span><span class="pi">:</span> <span class="s">self</span>
    <span class="na">submodules</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">AzureStaticWebApp@0</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="na">app_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">/_site'</span>
      <span class="na">api_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">api'</span>
      <span class="na">output_location</span><span class="pi">:</span> <span class="s1">'</span><span class="s">/_site'</span>
      <span class="na">azure_static_web_apps_api_token</span><span class="pi">:</span> <span class="s">$(deployment_token)</span>

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

<p>At the root of your Jeykll blog add a new file called <code class="language-plaintext highlighter-rouge">azure-pipeline.yml</code> and add the above as the contents.</p>

<p>Don’t worry about the <code class="language-plaintext highlighter-rouge">api_location: 'api'</code> section - it does nothing and frankly I’m scared to touch it.</p>

<h1 id="azure-devops---im-sorry">Azure Devops - I’m sorry.</h1>

<p><strong>As a heads up I had this blog under an old alias and let the domain expire (like a fool). I’ve migrated it to this domain, so the names look a little off!</strong></p>

<p>First, create the Azure Static Web App. Once it’s deployed, you should land on the dashboard for the resource:</p>

<p><img src="https://i.imgur.com/WNGBMuU.png" alt="Azure Devops - Static Web App With Jekyll" /></p>

<p>Here you can set up your custom domain if you like - the verification for the TXT DNS addition took 3 (!) days to fully detect in the portal. We’ll skip this step for now.</p>

<p>Whilst on Overview, find the <code class="language-plaintext highlighter-rouge">Manage Deployment Token</code> button on the right hand side.</p>

<p><img src="https://i.imgur.com/Mh4F6gs.png" alt="Azure Devops - Manage Deployment Token" /></p>

<p>Click the button (of course), copy the token or keep the blade open for the next step.</p>

<p>Go to your DevOps organisation -&gt; Project containing the blog repository -&gt; Pipelines.</p>

<p><strong>You won’t have the “Blog” pipeline there, don’t worry!</strong></p>

<p><img src="https://i.imgur.com/K71qLWI.png" alt="Azure Devops - New Pipeline for Jekyll Blog" /></p>

<p>Click on new pipeline to begin sinning like me</p>

<p><img src="https://i.imgur.com/W7X3oux.png" alt="Azure Devops - New Pipeline" /></p>

<p>Select the <code class="language-plaintext highlighter-rouge">Azure Repos Git</code> option, assuming that’s where your blog repository is stored.</p>

<p><img src="https://i.imgur.com/YklnYR7.png" alt="Azure Devops - VCS select" /></p>

<p>Select your repository</p>

<p><img src="https://i.imgur.com/xYX3uMb.png" alt="Azure devops - repo select" /></p>

<p>Now that you’ve saved our sinful pipeline <code class="language-plaintext highlighter-rouge">.yml</code> file, select <em>Existing Azure Pipelines YAML file</em></p>

<p><img src="https://i.imgur.com/RJPnFwo.png" alt="Azure Devops - Existing YAML file" /></p>

<p>Select the <code class="language-plaintext highlighter-rouge">azure-pipelines.yml</code> file from the root directory. For whatever reason Jekyll builds a copy to the <code class="language-plaintext highlighter-rouge">_site</code> directory, be sure <strong>not</strong> to use that one. Click <code class="language-plaintext highlighter-rouge">Continue</code> once you’re done.</p>

<p><img src="https://i.imgur.com/YSdGLO2.png" alt="Azure Devops - YML file select" /></p>

<p>Our <del>great</del> functional pipeline text will load in</p>

<p><img src="https://i.imgur.com/1GkALI3.png" alt="Azure Devops - Pipeline Review" /></p>

<p>Now’s the time to link the pipeline to our new, shiny Azure Static Web App. Click <code class="language-plaintext highlighter-rouge">Variables</code> in the top right</p>

<p><img src="https://i.imgur.com/7oKsEvr.png" alt="Azure Devops - Variables" /></p>

<p>Click <code class="language-plaintext highlighter-rouge">New Variable</code></p>

<p><img src="https://i.imgur.com/Ef6tOg9.png" alt="Azure Devops - New Variable" /></p>

<p>If you take a look at the bottom of our <code class="language-plaintext highlighter-rouge">azure-pipelines</code> file, we have a <code class="language-plaintext highlighter-rouge">$variable</code> called <code class="language-plaintext highlighter-rouge">deployment_token</code>. That’s the name of our variable. Make sure to add the token from our Static Web App blade from earlier to the <code class="language-plaintext highlighter-rouge">value</code> field:</p>

<p><img src="https://i.imgur.com/yY9rvt2.png" alt="Azure Devops - Variable Added" /></p>

<p>You can mark it as secret if you like, I haven’t because my partner can’t even find the monitor power button let alone understand Azure Pipelines.</p>

<p>You should see your new variable appear in the list, click save.</p>

<p><img src="https://i.imgur.com/z448xg4.png" alt="Azure Devops - Variable saved" /></p>

<p>Now save your pipeline by using the arrow on the <code class="language-plaintext highlighter-rouge">Run</code> button to show the hidden, very useful option</p>

<p><img src="https://i.imgur.com/APPyFYX.png" alt="i cant be bothered anymore" /></p>

<p>Awesome, your pipeline is set up. Now every time you push to <code class="language-plaintext highlighter-rouge">master</code>, the <code class="language-plaintext highlighter-rouge">_site</code> directory will deploy to your Azure Static Web App. <a href="https://www.youtube.com/watch?v=OUXvrWeQU0g">Please clap</a></p>

<h1 id="building-and-publishing-your-site">Building and publishing your site</h1>

<p>Now for the fun, extra scuffed section.</p>

<p>Every time a new blog post gets added, we need to build the blog. This converts the <code class="language-plaintext highlighter-rouge">.md</code> files containing our very important ramblings into readable <code class="language-plaintext highlighter-rouge">HTML</code> files. How exciting!</p>

<p>Now I’m lazy, if you couldn’t already tell. I can’t be bothered to write nice commits for things people will never see, so I wrote a <del>horrendous</del> functional script to speed this part up too.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle exec jekyll build | git add . | git commit -m "new post" | git pull | git push
</code></pre></div></div>

<p>Here we’re doing a few bits:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">bundle exec jekyll build</code> builds the <code class="language-plaintext highlighter-rouge">.md</code> files into <code class="language-plaintext highlighter-rouge">.html</code> files for the posts.</li>
  <li>Add all the new files to our repoitory.</li>
  <li>Commit them with a very descriptive commit message.</li>
  <li>Pull the latest changes in case we did a drunk ramble directly into DevOps (don’t judge me)</li>
  <li>Push the changes locally.</li>
</ul>

<p>I threw that crap into a <code class="language-plaintext highlighter-rouge">.ps1</code> PowerShell script and added it to the root of my blog.</p>

<p>Now, with the combined strength of our scuffed pipeline and horrendous git practices that my professors are definitely weeping at, we can write a new blog post and publish it with just a script. How far technology has come.</p>

<h1 id="after-thoughts">After thoughts</h1>

<p>I’m aware this is an awful way of ignoring learning proper CI/CD practices. I’ll live with myself. It works, nobody else is writing to this mess of a blog and I frankly don’t care about best practices for a micro-project.</p>

<p>Enjoy!</p>]]></content><author><name>James Gould</name><email>contact@jamesgould.dev</email></author><category term="jekyll" /><category term="azure devops" /><summary type="html"><![CDATA[Introduction]]></summary></entry></feed>