<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://strandweb.dk/feed.xml" rel="self" type="application/atom+xml" /><link href="https://strandweb.dk/" rel="alternate" type="text/html" /><updated>2026-07-25T11:00:08+00:00</updated><id>https://strandweb.dk/feed.xml</id><title type="html">Andreas Strandfelt - Azure MVP</title><subtitle>Azure MVP and cloud specialist sharing insights on Azure, .NET, and software development.</subtitle><author><name>Andreas Strandfelt</name></author><entry><title type="html">Azure region opens in Denmark: First look</title><link href="https://strandweb.dk/2026/03/azure-region-opens-in-denmark-first-look/" rel="alternate" type="text/html" title="Azure region opens in Denmark: First look" /><published>2026-03-17T19:28:58+00:00</published><updated>2026-03-17T19:28:58+00:00</updated><id>https://strandweb.dk/2026/03/azure-region-opens-in-denmark-first-look</id><content type="html" xml:base="https://strandweb.dk/2026/03/azure-region-opens-in-denmark-first-look/"><![CDATA[<p>Last week, the first Danish region in Azure finally opened, after being <a href="https://www.computerworld.dk/art/291646/microsoft-opgiver-at-aabne-sine-nye-danske-datacentre-til-tiden-kan-ikke-naa-at-blive-klar">delayed</a> in 2025.</p>

<p>Currently, only a subset of Azure features are supported. I recommend you visit azurecharts.com for the most up-to-date view of the products available: <a href="https://azurecharts.com/regions/?r=denmark-east">https://azurecharts.com/regions/?r=denmark-east</a></p>

<p>One thing is the products available. Another thing is the SKU’s. As a start, I realized that new Azure App Service Premium v4 are not available. Only Premium v3 (Pv3) is available.</p>

<p>Unfortunately, I also found that <a href="/2025/04/tips-for-premium-plans-to-flex-consumption/">my favorite</a> Azure Function offering, Azure Flex Consumption, is not offered in the region yet.</p>

<h2 id="cost-insights">Cost insights</h2>

<p>I have made some analysis on the costs. It seems that costs are a quite a bit higher than comparable regions, West Europe, North Europe and Sweden Central. Here is an example for Azure App Services.</p>

<p>Since Pv4 is not available in Denmark East yet, I chose to look at the P0v3 Linux sku. Below is a comparison of the costs, from lower to higher, using the cheapest region, North Europe, as the index.</p>

<table>
  <thead>
    <tr>
      <th>Region</th>
      <th>Price (USD/Hour)</th>
      <th>Index</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>North Europe</td>
      <td>$0.08400</td>
      <td>100 %</td>
    </tr>
    <tr>
      <td>Sweden Central</td>
      <td>$0,08900</td>
      <td>106 %</td>
    </tr>
    <tr>
      <td>West Europe</td>
      <td>$0,08900</td>
      <td>106 %</td>
    </tr>
    <tr>
      <td>Denmark East</td>
      <td>$0,10075</td>
      <td>120 %</td>
    </tr>
  </tbody>
</table>

<p>So the new region is 20 % more expensive than neighboring regions for Azure app service.</p>

<p>I also checked for Azure SQL and Azure Blob storage. In both cases, the prices were the same for all four regions. Let’s hope the compute costs will go the same way.</p>

<h2 id="whats-next">What’s next?</h2>

<p>The next datacenter in Europe, Denmark West, is already underway. The name is very suiting, as it will be built near Varde and Esbjerg. Some of the most Western parts of Denmark. And even closer to me. Exciting times. :-)<br />
More details (in Danish): <a href="https://news.microsoft.com/source/emea/features/microsoft-offentliggoer-planer-om-at-opfoere-ny-datacenterregion-i-vestjylland/?lang=da&amp;ocid=AIDN%2FA_LINKEDIN_oo_spl100009232194198">https://news.microsoft.com/source/emea/features/microsoft-offentliggoer-planer-om-at-opfoere-ny-datacenterregion-i-vestjylland/</a></p>]]></content><author><name>Andreas Strandfelt</name></author><category term="Azure" /><category term="azure" /><summary type="html"><![CDATA[Last week, the first Danish region in Azure finally opened, after being delayed in 2025.]]></summary></entry><entry><title type="html">How I secured my MVP Summit spot using automation</title><link href="https://strandweb.dk/2025/09/how-i-secured-my-mvp-summit-spot-using-automation/" rel="alternate" type="text/html" title="How I secured my MVP Summit spot using automation" /><published>2025-09-16T19:15:39+00:00</published><updated>2025-09-16T19:15:39+00:00</updated><id>https://strandweb.dk/2025/09/how-i-secured-my-mvp-summit-spot-using-automation</id><content type="html" xml:base="https://strandweb.dk/2025/09/how-i-secured-my-mvp-summit-spot-using-automation/"><![CDATA[<p>Following receiving my MVP Award last month, I really wanted to go to the <a href="https://summit.microsoft.com/en-us/">Microsoft MVP Summit</a> in Redmond next year . However, the number of seats for In-Person attendance is limited, and you never actually know when registration opens - until it does.</p>

<p>I decided this was a great opportunity for me to test the Azure OpenAI services. The idea was to get the HTML body of the website which announces registration and make GPT parse the text and respond whether registration was open or not.</p>

<p>Azure Functions was a perfect fit for this. A Flex Consumption app that runs every 5 minutes with a timer trigger is one of the easiest things in the world to setup, and it has a very low cost.</p>

<p>Finally, I needed some way to get notified when registration opens. I considered using <a href="https://learn.microsoft.com/en-us/azure/communication-services/overview">Azure Communication Services</a> to send me a text message or a call, but it was too much of a hassle for this small project, as you need to register a phone number with a provider and pay for that number.<br />
Instead, I remembered that Home Assistant offers webhooks for automations, and since I have a Home Assistant Server at home and the Home Assistant companion app on my phone, that was a perfect match for this use case.</p>

<h2 id="the-azure-function">The Azure Function</h2>

<p>The function runs on a timer every 5 minutes and performs the following steps:</p>

<ul>
  <li>Download HTML from the summit page</li>
  <li>Run a fast keyword check (just because you can)</li>
  <li>If inconclusive, call Azure OpenAI to classify the body content as OPEN or CLOSED</li>
  <li>If OPEN, trigger a Home Assistant webhook which drives a mobile notification</li>
</ul>

<h3 id="prerequisites">Prerequisites</h3>

<ul>
  <li>Azure OpenAI resource + deployed model (I used <code class="language-plaintext highlighter-rouge">gpt-5-mini</code>)</li>
  <li>Function App (or local Function Core Tools) targeting .NET 8 isolated worker</li>
  <li>Configured environment variables: <code class="language-plaintext highlighter-rouge">AZURE_OPENAI_ENDPOINT</code>, <code class="language-plaintext highlighter-rouge">AZURE_OPENAI_KEY</code>, <code class="language-plaintext highlighter-rouge">AZURE_OPENAI_DEPLOYMENT</code></li>
  <li>Home Assistant instance with an automation webhook URL and public access (I use <a href="https://www.nabucasa.com/">Nabu Casa</a>)</li>
  <li>You must add the <a href="https://www.nuget.org/packages/Azure.AI.OpenAI">Azure.AI.OpenAI</a> NuGet package to your project</li>
</ul>

<h3 id="heuristic-check">Heuristic check</h3>

<p>The heuristic avoids an AI call when obvious registration phrases exist. These were just a guess from my side and you could put in anything in here.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="kt">bool</span> <span class="nf">HeuristicCheck</span><span class="p">(</span><span class="kt">string</span> <span class="n">html</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">lowered</span> <span class="p">=</span> <span class="n">html</span><span class="p">.</span><span class="nf">ToLowerInvariant</span><span class="p">();</span>
    <span class="kt">var</span> <span class="n">indicators</span> <span class="p">=</span> <span class="k">new</span><span class="p">[]</span>
    <span class="p">{</span>
        <span class="s">"register now"</span><span class="p">,</span>
        <span class="s">"registration open"</span><span class="p">,</span>
        <span class="s">"register today"</span><span class="p">,</span>
        <span class="s">"sign up"</span>
    <span class="p">};</span>
    <span class="k">return</span> <span class="n">indicators</span><span class="p">.</span><span class="nf">Any</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">lowered</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="n">p</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="azure-openai-classification">Azure OpenAI classification</h3>

<p>First, setup an AzureOpenAIClient. I did it in Program.cs:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddSingleton</span><span class="p">(</span><span class="n">sp</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">AzureOpenAIClient</span><span class="p">(</span>
    <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="n">Environment</span><span class="p">.</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s">"AZURE_OPENAI_ENDPOINT"</span><span class="p">)),</span>
    <span class="k">new</span> <span class="nf">AzureKeyCredential</span><span class="p">(</span><span class="n">Environment</span><span class="p">.</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s">"AZURE_OPENAI_KEY"</span><span class="p">))));</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">&lt;body&gt;</code> section is sent, which reduces token usage:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="kt">bool</span><span class="p">&gt;</span> <span class="nf">DetermineRegistrationOpenAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">html</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nf">HeuristicCheck</span><span class="p">(</span><span class="n">html</span><span class="p">))</span> <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="nf">MissingOpenAiConfig</span><span class="p">())</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>

    <span class="kt">var</span> <span class="n">body</span> <span class="p">=</span> <span class="nf">ExtractBodyContent</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="p">??</span> <span class="n">html</span><span class="p">;</span>
    <span class="kt">var</span> <span class="n">chatClient</span> <span class="p">=</span> <span class="n">azureOpenAIClient</span><span class="p">.</span><span class="nf">GetChatClient</span><span class="p">(</span><span class="n">_openAiDeployment</span><span class="p">);</span>

    <span class="kt">var</span> <span class="n">messages</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">ChatMessage</span><span class="p">&gt;</span>
    <span class="p">{</span>
        <span class="k">new</span> <span class="nf">SystemChatMessage</span><span class="p">(</span><span class="s">"You classify event registration state based on HTML body content. Reply only with OPEN or CLOSED."</span><span class="p">),</span>
        <span class="k">new</span> <span class="nf">UserChatMessage</span><span class="p">(</span><span class="n">body</span><span class="p">)</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">chatClient</span><span class="p">.</span><span class="nf">CompleteChatAsync</span><span class="p">(</span><span class="n">messages</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
    <span class="kt">var</span> <span class="n">answer</span> <span class="p">=</span> <span class="n">response</span><span class="p">.</span><span class="n">Value</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">First</span><span class="p">().</span><span class="n">Text</span><span class="p">.</span><span class="nf">Trim</span><span class="p">().</span><span class="nf">ToUpperInvariant</span><span class="p">();</span>
    <span class="k">return</span> <span class="n">answer</span> <span class="p">==</span> <span class="s">"OPEN"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="webhook-notification">Webhook notification</h3>

<p>When registration is detected as open, a JSON POST to the Home Assistant webhook triggers a mobile push notification:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">TriggerWebhookAsync</span><span class="p">(</span><span class="n">CancellationToken</span> <span class="n">ct</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">payload</span> <span class="p">=</span> <span class="k">new</span> <span class="p">{</span> <span class="n">message</span> <span class="p">=</span> <span class="s">"Microsoft Summit registration appears OPEN."</span><span class="p">,</span> <span class="n">url</span> <span class="p">=</span> <span class="n">SummitUri</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(),</span> <span class="n">detectedAtUtc</span> <span class="p">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">UtcNow</span> <span class="p">};</span>
    <span class="k">using</span> <span class="nn">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">PostAsJsonAsync</span><span class="p">(</span><span class="n">WebhookUri</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(!</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
        <span class="n">_logger</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"Webhook call failed: {Status}"</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>

<h3 id="configuration-localsettingsjson">Configuration (local.settings.json)</h3>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Values"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"FUNCTIONS_WORKER_RUNTIME"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dotnet-isolated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AZURE_OPENAI_ENDPOINT"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://&lt;your-openai-resource&gt;.openai.azure.com/"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AZURE_OPENAI_KEY"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;key&gt;"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AZURE_OPENAI_DEPLOYMENT"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gpt-5-mini"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="the-home-assistant-automation">The Home Assistant automation</h2>

<p>Create a new automation using the <em>Webhook</em> trigger. Here is my source:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">alias</span><span class="pi">:</span> <span class="s">Summit registration</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
<span class="na">triggers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">trigger</span><span class="pi">:</span> <span class="s">webhook</span>
    <span class="na">allowed_methods</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">POST</span>
      <span class="pi">-</span> <span class="s">PUT</span>
    <span class="na">local_only</span><span class="pi">:</span> <span class="no">false</span>
    <span class="na">webhook_id</span><span class="pi">:</span> <span class="s">summit-registration-OMITTED</span>
<span class="na">conditions</span><span class="pi">:</span> <span class="pi">[]</span>
<span class="na">actions</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">device_id</span><span class="pi">:</span> <span class="s">OMITTED</span>
    <span class="na">domain</span><span class="pi">:</span> <span class="s">mobile_app</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">notify</span>
    <span class="na">title</span><span class="pi">:</span> <span class="s">Summit registration open!</span>
    <span class="na">message</span><span class="pi">:</span> <span class="s">Registration is open</span>
    <span class="na">data</span><span class="pi">:</span>
      <span class="na">actions</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">action</span><span class="pi">:</span> <span class="s">URI</span>
          <span class="na">title</span><span class="pi">:</span> <span class="s">Open Url</span>
          <span class="na">uri</span><span class="pi">:</span> <span class="s">https://summit.microsoft.com/en-us/</span>
<span class="na">mode</span><span class="pi">:</span> <span class="s">single</span>
</code></pre></div></div>

<h3 id="security-notes">Security notes</h3>

<ul>
  <li>Webhook URL should be treated as a secret.</li>
</ul>

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

<ul>
  <li>Most of my code was written with GitHub copilot in Agent mode with GPT-5</li>
  <li>My costs to run this for a few days, including the Azure AI Foundry containing the gpt-5-mini model, added up to ~0.1 USD per day. And since it was announced one week ahead that registration would open some time this week, it added up to less than $1 to have this running for me.</li>
</ul>]]></content><author><name>Andreas Strandfelt</name></author><category term="Azure" /><category term=".NET" /><category term="azure ai foundry" /><category term="azure functions" /><category term="mvp" /><category term="openai" /><summary type="html"><![CDATA[Following receiving my MVP Award last month, I really wanted to go to the Microsoft MVP Summit in Redmond next year . However, the number of seats for In-Person attendance is limited, and you never actually know when registration opens - until it does.]]></summary></entry><entry><title type="html">Azure MVP award</title><link href="https://strandweb.dk/2025/08/azure-mvp-award/" rel="alternate" type="text/html" title="Azure MVP award" /><published>2025-08-05T12:44:20+00:00</published><updated>2025-08-05T12:44:20+00:00</updated><id>https://strandweb.dk/2025/08/azure-mvp-award</id><content type="html" xml:base="https://strandweb.dk/2025/08/azure-mvp-award/"><![CDATA[<p>I am very excited to share that I have been accepted as a <a href="https://mvp.microsoft.com/en-US/MVP/profile/f269b4e1-4912-4819-8162-b2690783c8a7">Microsoft MVP</a> within the Microsoft Azure Application PaaS technology area.</p>

<p>I am very proud to say that I am the only MVP within this area in Denmark.</p>

<p>I look forward to hopefully greet a lot of other MVP’s at the MVP Summit in Redmond next year.</p>]]></content><author><name>Andreas Strandfelt</name></author><category term="Azure" /><category term="Azure Functions" /><category term="azure" /><category term="azure functions" /><category term="mvp" /><summary type="html"><![CDATA[I am very excited to share that I have been accepted as a Microsoft MVP within the Microsoft Azure Application PaaS technology area.]]></summary></entry><entry><title type="html">Tips for moving Azure Functions from Premium plans to Flex Consumption</title><link href="https://strandweb.dk/2025/04/tips-for-premium-plans-to-flex-consumption/" rel="alternate" type="text/html" title="Tips for moving Azure Functions from Premium plans to Flex Consumption" /><published>2025-04-29T15:49:06+00:00</published><updated>2025-04-29T15:49:06+00:00</updated><id>https://strandweb.dk/2025/04/tips-for-premium-plans-to-flex-consumption</id><content type="html" xml:base="https://strandweb.dk/2025/04/tips-for-premium-plans-to-flex-consumption/"><![CDATA[<p>Azure Flex Consumption Functions went <a href="https://techcommunity.microsoft.com/blog/appsonazureblog/azure-functions-flex-consumption-is-now-generally-available/4298778">GA in November 2024</a>. We have been using them extensively since they came out in <a href="https://techcommunity.microsoft.com/blog/appsonazureblog/announcing-public-preview-of-azure-functions-flex-consumption/4139028">public preview</a> in May 2024, and we are very happy about this addition to the Azure Function SKU options.</p>

<p>When considering moving your Azure Functions to Flex Consumption, some limitations must be considered. These are described in the <a href="https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-plan#considerations">Flex Consumption hosting documentation</a>.<br />
However, in some cases, the limitations can be mitigated. Below, I will describe how to mitigate some of the limitations with very few lines of code.</p>

<h2 id="load-certificates-for-httpclients">Load certificates for HttpClients</h2>

<p>In Flex Consumption, it is currently not supported to load certificates from the managed certificate store using the app setting linked to an Azure Key Vault certificate.</p>

<p>However, you can still access Key Vault certificates from your Functions in Flex Consumption if you need to.</p>

<p>A very common use case is to load the certificates from a key vault to use them with client certificate authentication in an HttpClient. To solve this use case in a Flex Consumption app, you can refer to the certificate in from a key vault using the secret URL of the certificate. If you refer to the secret URL of a key vault certificate, you get the certificate as a Base64-encoded string, which you can decode and add as a client certificate when you configure your client.<br />
In your app settings, e.g. in Bicep, refer to the secret like this:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Settings__Certificate: '@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mycertificate)'
</code></pre></div></div>

<p>Notice the <code class="language-plaintext highlighter-rouge">/secrets/</code> path in the URL and not <code class="language-plaintext highlighter-rouge">/certificates/</code>. Even though this is stored as a certificate in the vault, you can access it through the <a href="https://learn.microsoft.com/en-us/rest/api/keyvault/secrets/get-secret/get-secret?view=rest-keyvault-secrets-7.4&amp;tabs=HTTP">secrets API</a> to retrieve it as a Base64 encoded string.</p>

<p>Then, using the Base64 encoded string in an app setting, you configure the HttpClient as follows in your Program.cs:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Security.Cryptography.X509Certificates</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.DependencyInjection</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.Hosting</span><span class="p">;</span>

<span class="kt">var</span> <span class="n">host</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HostBuilder</span><span class="p">()</span>
  <span class="p">.</span><span class="nf">ConfigureFunctionsWebApplication</span><span class="p">()</span>
  <span class="p">.</span><span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">s</span> <span class="p">=&gt;</span>
  <span class="p">{</span>
    <span class="n">s</span><span class="p">.</span><span class="n">AddOptionsSection</span><span class="p">&lt;</span><span class="n">MySettings</span><span class="p">&gt;(</span><span class="s">"Settings"</span><span class="p">);</span>
    <span class="kt">var</span> <span class="n">settings</span> <span class="p">=</span> <span class="n">s</span><span class="p">.</span><span class="n">GetOptions</span><span class="p">&lt;</span><span class="n">MySettings</span><span class="p">&gt;();</span>

    <span class="n">s</span><span class="p">.</span><span class="n">AddHttpClient</span><span class="p">&lt;</span><span class="n">MyService</span><span class="p">&gt;()</span>
      <span class="p">.</span><span class="nf">ConfigurePrimaryHttpMessageHandler</span><span class="p">(()</span> <span class="p">=&gt;</span>
      <span class="p">{</span>
        <span class="kt">var</span> <span class="n">handler</span> <span class="p">=</span> <span class="k">new</span> <span class="n">HttpClientHandler</span> <span class="p">{</span> <span class="n">UseCookies</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">ClientCertificateOptions</span> <span class="p">=</span> <span class="n">ClientCertificateOption</span><span class="p">.</span><span class="n">Manual</span> <span class="p">};</span>
        <span class="kt">var</span> <span class="n">certificate</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">X509Certificate2</span><span class="p">(</span><span class="n">Convert</span><span class="p">.</span><span class="nf">FromBase64String</span><span class="p">(</span><span class="n">settings</span><span class="p">.</span><span class="n">Certificate</span><span class="p">));</span>
        <span class="n">handler</span><span class="p">.</span><span class="n">ClientCertificates</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">certificate</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">handler</span><span class="p">;</span>
      <span class="p">});</span>
  <span class="p">})</span>
  <span class="p">.</span><span class="nf">Build</span><span class="p">();</span>

<span class="k">await</span> <span class="n">host</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">();</span>
</code></pre></div></div>

<h2 id="cipher-suites">Cipher suites</h2>

<p>On Flex Consumption, I have found that the set of default TLS Cipher Suites is more restrictive than what is configured per default in Consumption and Premium plans.<br />
If you see errors from the runtime like <code class="language-plaintext highlighter-rouge">Interop+Crypto+OpenSslCryptographicException</code> and <code class="language-plaintext highlighter-rouge">The SSL connection could not be established, see inner exception. Authentication failed, see inner exception. SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL. error:0A000410:SSL routines::sslv3 alert handshake failure</code> when calling and external API’s that do not support TLS1.3, I suggest you try the following:</p>

<p>Use ssllabs.com to get the list of supported cipher suites by the external endpoint: <a href="https://www.ssllabs.com/ssltest/">https://www.ssllabs.com/ssltest/</a></p>

<p><img src="/wp-content/uploads/2025/04/image-1024x350.png" alt="" /></p>

<p><strong>WARNING:</strong> The code below will modify, and, most likely, weaken, the security of your outbound HTTP calls. Only do this if you have to, and have no control over cipher suites of the remote API.</p>

<p>Configure the TLS Protocol and Cipher Suites on the HttpClient handler using the SocketsHttpHandler as shown below:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Net.Security</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.DependencyInjection</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.Hosting</span><span class="p">;</span>

<span class="kt">var</span> <span class="n">host</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HostBuilder</span><span class="p">()</span>
  <span class="p">.</span><span class="nf">ConfigureFunctionsWebApplication</span><span class="p">()</span>
  <span class="p">.</span><span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">s</span> <span class="p">=&gt;</span>
  <span class="p">{</span>
    <span class="n">s</span><span class="p">.</span><span class="nf">AddHttpClient</span><span class="p">(</span><span class="s">"tls1.1"</span><span class="p">).</span><span class="nf">ConfigurePrimaryHttpMessageHandler</span><span class="p">(()</span> <span class="p">=&gt;</span>
    <span class="k">new</span> <span class="nf">SocketsHttpHandler</span><span class="p">()</span>
    <span class="p">{</span>
      <span class="n">SslOptions</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SslClientAuthenticationOptions</span>
      <span class="p">{</span>
        <span class="n">EnabledSslProtocols</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">SslProtocols</span><span class="p">.</span><span class="n">Tls12</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">SslProtocols</span><span class="p">.</span><span class="n">Tls13</span><span class="p">,</span>
        <span class="n">CipherSuitesPolicy</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CipherSuitesPolicy</span><span class="p">(</span>
        <span class="k">new</span><span class="p">[]</span>
        <span class="p">{</span>
          <span class="c1">// Default</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256</span><span class="p">,</span>

          <span class="c1">// Additional suites (replace with your own)</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_RSA_WITH_AES_128_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_RSA_WITH_AES_256_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_DHE_RSA_WITH_AES_128_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_DHE_RSA_WITH_AES_256_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_RSA_WITH_3DES_EDE_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA</span><span class="p">,</span>
          <span class="n">TlsCipherSuite</span><span class="p">.</span><span class="n">TLS_AES_128_GCM_SHA256</span><span class="p">,</span>
        <span class="p">}),</span>
      <span class="p">},</span>

    <span class="p">});</span>
  <span class="p">})</span>
  <span class="p">.</span><span class="nf">Build</span><span class="p">();</span>

<span class="k">await</span> <span class="n">host</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">();</span>
</code></pre></div></div>

<h2 id="accessing-network-restricted-key-vaults">Accessing network-restricted Key Vaults</h2>

<p>A current limitation of Flex Consumption at the time of writing (April 2025) is that you are unable to access secrets in a network-restricted Azure Key Vault by using the SecretUri reference in the app settings of the Function App as follows: <code class="language-plaintext highlighter-rouge">Microsoft.KeyVault(@SecretUri=https://myvault.vault.azure.net/secrets/mysecret)</code></p>

<p>Instead, you can use the Key Vault SDK to retrieve the secrets from code with the following code in your Program.cs:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Azure.Identity</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Azure.Security.KeyVault.Secrets</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.DependencyInjection</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.Hosting</span><span class="p">;</span>

<span class="kt">var</span> <span class="n">host</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HostBuilder</span><span class="p">()</span>
  <span class="p">.</span><span class="nf">ConfigureFunctionsWorkerDefaults</span><span class="p">()</span>
  <span class="p">.</span><span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">services</span> <span class="p">=&gt;</span>
  <span class="p">{</span>
    <span class="kt">var</span> <span class="n">keyVaultUri</span> <span class="p">=</span> <span class="s">"https://myvault.vault.azure.net/"</span><span class="p">;</span>
    <span class="n">services</span><span class="p">.</span><span class="nf">AddSingleton</span><span class="p">(</span><span class="k">new</span> <span class="nf">SecretClient</span><span class="p">(</span><span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="nf">Uri</span><span class="p">(</span><span class="n">keyVaultUri</span><span class="p">),</span> <span class="k">new</span> <span class="nf">DefaultAzureCredential</span><span class="p">()));</span>
  <span class="p">})</span>
  <span class="p">.</span><span class="nf">Build</span><span class="p">();</span>

<span class="k">await</span> <span class="n">host</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">();</span>
</code></pre></div></div>

<p>Now, you can inject the SecretClient into your services where needed.</p>

<p>I expect this to be a temporary limitation, but now you have a much better alternative than removing the network restrictions from the Key Vault.</p>]]></content><author><name>Andreas Strandfelt</name></author><category term=".NET" /><category term="Azure" /><category term="Azure Functions" /><category term="Flex Consumption" /><category term=".NET" /><category term="azure" /><category term="azure functions" /><category term="blog" /><category term="code" /><category term="dotnet" /><category term="example" /><category term="flex consumption" /><category term="key vault" /><summary type="html"><![CDATA[Azure Flex Consumption Functions went GA in November 2024. We have been using them extensively since they came out in public preview in May 2024, and we are very happy about this addition to the Azure Function SKU options.]]></summary></entry></feed>