<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Justin Yoo</title>
    <description>The latest articles on Forem by Justin Yoo (@justinyoo).</description>
    <link>https://forem.com/justinyoo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F224115%2F6a89c2a3-7b39-4c55-a294-ca9fa37091c8.jpg</url>
      <title>Forem: Justin Yoo</title>
      <link>https://forem.com/justinyoo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/justinyoo"/>
    <language>en</language>
    <item>
      <title>One Command Deploying .NET Apps to Azure Container Apps: azd up</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Fri, 06 Sep 2024 16:13:09 +0000</pubDate>
      <link>https://forem.com/azure/one-command-deploying-net-apps-to-azure-container-apps-azd-up-54do</link>
      <guid>https://forem.com/azure/one-command-deploying-net-apps-to-azure-container-apps-azd-up-54do</guid>
      <description>&lt;p&gt;In my previous blog posts of &lt;a href="https://dev.to/dotnet/different-containerising-options-for-net-developers-cne"&gt;containerising .NET apps&lt;/a&gt; and &lt;a href="https://dev.to/azure/you-dont-need-dockerfile-to-containerise-azure-functions-aaj"&gt;Function apps&lt;/a&gt;, I discussed how to containerise .NET apps and Azure Functions apps with and without &lt;code&gt;Dockerfile&lt;/code&gt;. However, deploying these containerised apps to &lt;a href="https://learn.microsoft.com/azure/container-apps/overview?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Azure Container Apps (ACA)&lt;/a&gt; is a different story.&lt;/p&gt;

&lt;p&gt;Since its release in May 2023, &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/overview?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Azure Developer CLI (&lt;code&gt;azd&lt;/code&gt;)&lt;/a&gt; has evolved significantly. &lt;code&gt;azd&lt;/code&gt; nowadays even automatically generates &lt;a href="https://learn.microsoft.com/azure/azure-resource-manager/bicep/overview?tabs=bicep?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Bicep&lt;/a&gt; files for us to immediately provision and deploy applications to Azure. With this feature, you only need the &lt;code&gt;azd up&lt;/code&gt; command for provisioning and deployment.&lt;/p&gt;

&lt;p&gt;Throughout this post, I'm going to discuss how to provision and deploy .NET apps including Azure Functions to ACA through just one command, &lt;code&gt;azd up&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find a sample code from:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/devkimchi" rel="noopener noreferrer"&gt;
        devkimchi
      &lt;/a&gt; / &lt;a href="https://github.com/devkimchi/azdevfying-dotnet-apps-on-aca" rel="noopener noreferrer"&gt;
        azdevfying-dotnet-apps-on-aca
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This provides sample .NET apps to be deployed onto Azure Container Apps through azd CLI
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;azdevfying .NET apps on Azure Container Apps&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This provides sample .NET apps to be deployed onto Azure Container Apps through &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?WT.mc_id=dotnet-149302-juyoo" rel="nofollow noopener noreferrer"&gt;Azure Developer CLI&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://visualstudio.microsoft.com/vs/?WT.mc_id=dotnet-149302-juyoo" rel="nofollow noopener noreferrer"&gt;Visual Studio 2022 17.10+&lt;/a&gt; or &lt;a href="https://code.visualstudio.com/?WT.mc_id=dotnet-149302-juyoo" rel="nofollow noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; with &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit&amp;amp;WT.mc_id=dotnet-149302-juyoo" rel="nofollow noopener noreferrer"&gt;C# Dev Kit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?WT.mc_id=dotnet-149302-juyoo" rel="nofollow noopener noreferrer"&gt;Azure Developer CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=dotnet-149302-juyoo" rel="nofollow noopener noreferrer"&gt;Azure Functions Core Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/desktop/" rel="nofollow noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/devkimchi/azdevfying-dotnet-apps-on-aca./images/architecture.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdevkimchi%2Fazdevfying-dotnet-apps-on-aca.%2Fimages%2Farchitecture.png" alt="Overall architecture"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Three .NET apps are provided in this repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/devkimchi/azdevfying-dotnet-apps-on-aca./WebApp/" rel="noopener noreferrer"&gt;Blazor web app&lt;/a&gt; as a frontend&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/devkimchi/azdevfying-dotnet-apps-on-aca./ApiApp/" rel="noopener noreferrer"&gt;ASP.NET Core Web API&lt;/a&gt; as a backend&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/devkimchi/azdevfying-dotnet-apps-on-aca./FuncApp/" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; as a backend&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can see the complete example including all the Bicep files by switching to the &lt;a href="https://github.com/devkimchi/azdevfying-dotnet-apps-on-aca/tree/azdevfied" rel="noopener noreferrer"&gt;&lt;code&gt;azdevfied&lt;/code&gt;&lt;/a&gt; branch.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;ol&gt;

&lt;li&gt;

&lt;p&gt;Login to Azure through Azure Developer CLI.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;azd auth login&lt;/pre&gt;

&lt;/div&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Run the following command to initialise &lt;code&gt;azd&lt;/code&gt;. It will ask you to choose options how to initialise. Follow the instructions.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;azd init&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once initialised, you will have both &lt;code&gt;.azure&lt;/code&gt; and &lt;code&gt;infra&lt;/code&gt; directories and both &lt;code&gt;next-steps.md&lt;/code&gt; and &lt;code&gt;azure.yaml&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;At this stage, the web app doesn't…&lt;/p&gt;


&lt;/li&gt;

&lt;/ol&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/devkimchi/azdevfying-dotnet-apps-on-aca" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;There are a few prerequisites to containerise .NET apps effectively.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/download/dotnet/8.0?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;.NET SDK 8.0+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://visualstudio.microsoft.com/?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt; or &lt;a href="https://code.visualstudio.com/?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; + &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;C# Dev Kit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/overview?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Azure Developer CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Azure Functions Core Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running the app locally
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/devkimchi/azdevfying-dotnet-apps-on-aca" rel="noopener noreferrer"&gt;sample app repository&lt;/a&gt; already includes the following apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/aspnet/core/blazor/?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt; app as a frontend&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis/overview?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;ASP.NET Core Web API&lt;/a&gt; app as a backend&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; app as another backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's make sure those apps running properly on your local machine. In order to run those apps locally, open three terminal windows and run the following commands on each terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1 - ASP.NET Core Web API&lt;/span&gt;
dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; ./ApiApp

&lt;span class="c"&gt;# Terminal 2 - Azure Functions&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ./FuncApp
dotnet clean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; func start

&lt;span class="c"&gt;# Terminal 3 - Blazor app&lt;/span&gt;
dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; ./WebApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your web browser and navigate to &lt;code&gt;https://localhost:5001&lt;/code&gt; to see the Blazor app running. Then navigate to &lt;code&gt;https://localhost:5001/weather&lt;/code&gt; to see the weather data fetched from the &lt;code&gt;ApiApp&lt;/code&gt; and the greetings populated from the &lt;code&gt;FuncApp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-01.png" alt="All apps up &amp;amp; running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's start using &lt;code&gt;azd&lt;/code&gt; to provision and deploy these apps to ACA. Make sure that you've already logged in to Azure with the &lt;code&gt;azd auth login&lt;/code&gt; command.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;azd init&lt;/code&gt; – Initialisation
&lt;/h2&gt;

&lt;p&gt;In order to provision and deploy the apps to ACA, you need to initialise the &lt;code&gt;azd&lt;/code&gt; configuration. Run the following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You'll be prompted to initialise the app. Choose the &lt;code&gt;Use code in the current directory&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-02.png" alt="Use code in the current directory"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;azd&lt;/code&gt; automatically detects your three apps as shown below. In addition to that, it says it will use Azure Container Apps. Choose the &lt;code&gt;Confirm and continue initializing my app&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-03.png" alt="Confirm and continue"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The function app asks the target port number. Enter &lt;code&gt;80&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-04.png" alt="Enter the target port number"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally, it asks the environment name. Enter any name you want. I just entered &lt;code&gt;aca0906&lt;/code&gt; for now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-05.png" alt="Enter the environment name"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you've got two directories and two files generated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.azure&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;infra&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;next-steps.md&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;azure.yaml&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-06.png" alt="Directories and files generated"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under the &lt;code&gt;infra&lt;/code&gt; directory, there are bunch of Bicep files automatically generated through &lt;code&gt;azd init&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-07.png" alt="Bicep files generated"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a result of running the command, &lt;code&gt;azd init&lt;/code&gt;, you don't have to write all necessary Bicep files. Instead, it generates them for you, which significantly reduces the time for infrastructure provisioning. Now, you're ready to provision and deploy your apps to ACA. Let's move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;azd up&lt;/code&gt; – Provision and deployment
&lt;/h2&gt;

&lt;p&gt;All you need to run at this stage is:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then, it asks you to confirm the subscription and location to provision the resources. Choose the appropriate options and continue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-08.png" alt="Choose subscription and location for resource provisioning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All apps are containerised and deployed to ACA. Once the deployment is done, you can see the output as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-09.png" alt="Deployment done"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the web app URL and navigate to the &lt;code&gt;/weather&lt;/code&gt; page. But you will see the error as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-10.png" alt="Error on the web app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is because each app doesn't know where each other is. Therefore, you should update the Bicep files to let the web app know where the other apps are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update Bicep files – Service discovery
&lt;/h2&gt;

&lt;p&gt;Open the &lt;code&gt;infra/main.bicep&lt;/code&gt; file and update the &lt;code&gt;webApp&lt;/code&gt; resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module webApp './app/WebApp.bicep' = {
  name: 'WebApp'
  params: {
    ...
    // Add these two lines
    apiAppEndpoint: apiApp.outputs.uri
    funcAppEndpoint: funcApp.outputs.uri
  }
  scope: rg
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, open the &lt;code&gt;infra/app/WebApp.bicep&lt;/code&gt; file and add both &lt;code&gt;apiAppEndpoint&lt;/code&gt; and &lt;code&gt;funcAppEndpoint&lt;/code&gt; parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
@secure()
param appDefinition object

// Add these two lines
param apiAppEndpoint string
param funcAppEndpoint string
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the same file, change the &lt;code&gt;env&lt;/code&gt; variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
var env = map(filter(appSettingsArray, i =&amp;gt; i.?secret == null), i =&amp;gt; {
  name: i.name
  value: i.value
})

// After
var env = union(map(filter(appSettingsArray, i =&amp;gt; i.?secret == null), i =&amp;gt; {
  name: i.name
  value: i.value
}), [
  {
    name: 'API_ENDPOINT_URL'
    value: apiAppEndpoint
  }
  {
    name: 'FUNC_ENDPOINT_URL'
    value: funcAppEndpoint
  }
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change passes the API and Function app endpoints to the web app as environment variables, so that the web app knows where the other apps are.&lt;/p&gt;

&lt;p&gt;Once you've made the changes, run the &lt;code&gt;azd up&lt;/code&gt; command again. It will update the resources in ACA. After that, go to the web app URL and navigate to the &lt;code&gt;/weather&lt;/code&gt; page. You will see the weather data and greetings fetched from the API and Function apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F09%2Fazdevfying-dotnet-apps-to-aca-11.png" alt="All apps up &amp;amp; running"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;So far, I've discussed how to provision and deploy .NET apps including Azure Functions to ACA with just one command, &lt;code&gt;azd up&lt;/code&gt;. This is a very convenient way to deploy apps to Azure. However, to let the apps know each other, you should slightly tweak the auto-generated Bicep files. With this little tweak, all your .NET apps will be seamlessly provisioned and deployed to ACA.&lt;/p&gt;

&lt;p&gt;One more thing I'd like to mention here, though, is that, if you use &lt;a href="https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;.NET Aspire&lt;/a&gt;, this sort of service discovery is automatically handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  More about deploying .NET apps to ACA?
&lt;/h2&gt;

&lt;p&gt;If you want to learn more options about deploying .NET apps to ACA, the following links might be helpful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/container-apps/code-to-cloud-options?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Deployment options to ACA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/container-apps/quickstart-code-to-cloud?tabs=bash%2Ccsharp&amp;amp;WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;Publish .NET apps to ACA via Azure CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview?WT.mc_id=dotnet-149673-juyoo" rel="noopener noreferrer"&gt;.NET Aspire&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>containers</category>
      <category>azd</category>
      <category>azurecontainerapps</category>
    </item>
    <item>
      <title>You don't need Dockerfile to containerise Azure Functions</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Fri, 23 Aug 2024 10:10:10 +0000</pubDate>
      <link>https://forem.com/azure/you-dont-need-dockerfile-to-containerise-azure-functions-aaj</link>
      <guid>https://forem.com/azure/you-dont-need-dockerfile-to-containerise-azure-functions-aaj</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/dotnet/different-containerising-options-for-net-developers-cne"&gt;previous post&lt;/a&gt;, I discussed different containerising options for .NET developers. Now, let's slightly move the focus to &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; app. How can you containerise the Azure Functions app? Fortunately, &lt;a href="https://learn.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;Azure Functions Core Tools&lt;/a&gt; natively support it with &lt;code&gt;Dockerfile&lt;/code&gt;. But is the &lt;code&gt;Dockerfile&lt;/code&gt; the only option for containerising your .NET-based Azure Functions apps? Throughout this post, I'm going to discuss how Azure Functions apps can be containerised both with and without &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find a sample code from:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/devkimchi" rel="noopener noreferrer"&gt;
        devkimchi
      &lt;/a&gt; / &lt;a href="https://github.com/devkimchi/msbuild-for-functions-on-containers" rel="noopener noreferrer"&gt;
        msbuild-for-functions-on-containers
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This provides sample .NET function apps using container images with Dockerfile and with MSBuild on Docker Desktop.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;MSBuild for Azure Functions on Containers&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;As a .NET developer, when you build a container image for your &lt;a href="https://learn.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=dotnet-147942-juyoo" rel="nofollow noopener noreferrer"&gt;Azure Functions&lt;/a&gt; app, you can use the &lt;code&gt;Dockerfile&lt;/code&gt; to define the container image. However, you can also use the &lt;code&gt;dotnet publish&lt;/code&gt; command to build and publish the container image without a &lt;code&gt;Dockerfile&lt;/code&gt;. This repository provides sample .NET function apps using container images with &lt;code&gt;Dockerfile&lt;/code&gt; and with &lt;code&gt;dotnet publish&lt;/code&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/download/dotnet/8.0?WT.mc_id=dotnet-147942-juyoo" rel="nofollow noopener noreferrer"&gt;.NET SDK 8.0+&lt;/a&gt; with &lt;a href="https://learn.microsoft.com/dotnet/aspire/fundamentals/setup-tooling?WT.mc_id=dotnet-147942-juyoo" rel="nofollow noopener noreferrer"&gt;.NET Aspire workload&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://visualstudio.microsoft.com/?WT.mc_id=dotnet-147942-juyoo" rel="nofollow noopener noreferrer"&gt;Visual Studio&lt;/a&gt; or &lt;a href="https://code.visualstudio.com/?WT.mc_id=dotnet-147942-juyoo" rel="nofollow noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; + &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit" rel="nofollow noopener noreferrer"&gt;C# Dev Kit&lt;/a&gt; + &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions?WT.mc_id=dotnet-147942-juyoo" rel="nofollow noopener noreferrer"&gt;Azure Functions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=dotnet-147942-juyoo" rel="nofollow noopener noreferrer"&gt;Azure Functions Core Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/products/docker-desktop" rel="nofollow noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Make sure Docker Desktop is running before you start.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Run with &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/h3&gt;

&lt;/div&gt;


&lt;ol&gt;

&lt;li&gt;

&lt;p&gt;Run &lt;code&gt;func init&lt;/code&gt; to create a new Azure Functions app with Docker support.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;func init FunctionAppWithDockerfile --worker-runtime dotnet-isolated --docker --target-framework net8.0&lt;/pre&gt;

&lt;/div&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Confirm the &lt;code&gt;Dockerfile&lt;/code&gt; is created in the &lt;code&gt;FunctionAppWithDockerfile&lt;/code&gt; folder.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Run &lt;code&gt;func new&lt;/code&gt; to add a new HTTP trigger…&lt;/p&gt;


&lt;/li&gt;

&lt;/ol&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/devkimchi/msbuild-for-functions-on-containers" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;There are a few prerequisites to containerise .NET-based function apps effectively.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/download/dotnet/8.0?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;.NET SDK 8.0+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;Azure Functions Core Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://visualstudio.microsoft.com/?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt; or &lt;a href="https://code.visualstudio.com/?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; + &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;C# Dev Kit&lt;/a&gt; + &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Containerise with &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;With the Azure Functions Core Tools, you can create a new Azure Functions app with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;func init FunctionAppWithDockerfile &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--worker-runtime&lt;/span&gt; dotnet-isolated &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--docker&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--target-framework&lt;/span&gt; net8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may notice the &lt;code&gt;--docker&lt;/code&gt; option in the command. This option creates a &lt;code&gt;Dockerfile&lt;/code&gt; in the project directory. The &lt;code&gt;Dockerfile&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;installer-env&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /src/dotnet-function-app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /src/dotnet-function-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/site/wwwroot &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;dotnet publish &lt;span class="k"&gt;*&lt;/span&gt;.csproj &lt;span class="nt"&gt;--output&lt;/span&gt; /home/site/wwwroot

&lt;span class="c"&gt;# To enable ssh &amp;amp; remote debugging on app service change the base image to the one below&lt;/span&gt;
&lt;span class="c"&gt;# FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-appservice&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; AzureWebJobsScriptRoot=/home/site/wwwroot \&lt;/span&gt;
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few points on &lt;code&gt;Dockerfile&lt;/code&gt; to pick up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The base image for the runtime is &lt;code&gt;mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0&lt;/code&gt;. There are two other options available, &lt;code&gt;mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-slim&lt;/code&gt; and &lt;code&gt;mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-mariner&lt;/code&gt;. You can choose one of them based on your requirements.&lt;/li&gt;
&lt;li&gt;The function app is running on the &lt;code&gt;/home/site/wwwroot&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;It has two environment variables, &lt;code&gt;AzureWebJobsScriptRoot&lt;/code&gt; and &lt;code&gt;AzureFunctionsJobHost__Logging__Console__IsEnabled&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;There's no &lt;code&gt;ENTRYPOINT&lt;/code&gt; instruction defined.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These points will be used later on this post.&lt;/p&gt;

&lt;p&gt;Let's add a new function to the Azure Functions app with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;func new &lt;span class="nt"&gt;-n&lt;/span&gt; HttpExampleTrigger &lt;span class="nt"&gt;-t&lt;/span&gt; HttpTrigger &lt;span class="nt"&gt;-a&lt;/span&gt; anonymous
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's added, open the &lt;code&gt;HttpExampleTrigger.cs&lt;/code&gt; file and modify it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome to Azure Functions!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome to Azure Functions with Dockerfile!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can build the container image with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; funcapp:latest-dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have the container image for the Azure Functions app. You can run the container with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 7071:80 &lt;span class="nt"&gt;--name&lt;/span&gt; funcappdockerfile funcapp:latest-dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your function app is now up and running in a container. Open the browser and navigate to &lt;code&gt;http://localhost:7071/api/HttpExampleTrigger&lt;/code&gt; to see the function app running.&lt;/p&gt;

&lt;p&gt;Open your Docker Desktop and check the running container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F08%2Fcontainerising-azure-functions-without-dockerfile-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F08%2Fcontainerising-azure-functions-without-dockerfile-01.png" alt="Container running Azure Functions app #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you see the &lt;code&gt;Path&lt;/code&gt; and &lt;code&gt;Args&lt;/code&gt; value? The &lt;code&gt;Path&lt;/code&gt; value is &lt;code&gt;/opt/startup/start_nonappservice.sh&lt;/code&gt; and the &lt;code&gt;Args&lt;/code&gt; value is an empty array. In other words, the shell script, &lt;code&gt;start_nonappservice.sh&lt;/code&gt; is executed when the container starts, and it takes no arguments. Keep this information in mind. You'll use it later in this post.&lt;/p&gt;

&lt;p&gt;Once you're done, stop and remove the container with the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop funcappdockerfile &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;rm &lt;/span&gt;funcappdockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far, we've containerised the Azure Functions app with the &lt;code&gt;Dockerfile&lt;/code&gt;. But, as I mentioned before, there is another way to containerise the Azure Functions app without having &lt;code&gt;Dockerfile&lt;/code&gt;. Let's move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Containerise with &lt;code&gt;dotnet publish&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Because MSBuild natively supports containerisation, you can make use of the &lt;code&gt;dotnet publish&lt;/code&gt; command to build the container image for your function app. If you want to dynamically set the base container image, this option will be really useful. To do this, you might need to update your &lt;code&gt;.csproj&lt;/code&gt; file to include the containerisation settings. First of all, create a new function app without the &lt;code&gt;--docker&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;func init FunctionAppWithMSBuild &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--worker-runtime&lt;/span&gt; dotnet-isolated &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--target-framework&lt;/span&gt; net8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add a new function to the Azure Functions app with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;func new &lt;span class="nt"&gt;-n&lt;/span&gt; HttpExampleTrigger &lt;span class="nt"&gt;-t&lt;/span&gt; HttpTrigger &lt;span class="nt"&gt;-a&lt;/span&gt; anonymous
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's added, open the &lt;code&gt;HttpExampleTrigger.cs&lt;/code&gt; file and modify it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome to Azure Functions!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome to Azure Functions with MSBuild!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the fun part begins. Open the &lt;code&gt;FunctionAppWithMSBuild.csproj&lt;/code&gt; file and add the following node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ContainerEnvironmentVariable&lt;/span&gt;
      &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"AzureWebJobsScriptRoot"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"/home/site/wwwroot"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ContainerEnvironmentVariable&lt;/span&gt;
      &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"AzureFunctionsJobHost__Logging__Console__IsEnabled"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did you find that these two environment variables are the same as the ones in the &lt;code&gt;Dockerfile&lt;/code&gt;? Let's add another node to the &lt;code&gt;.csproj&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&lt;/span&gt; &lt;span class="na"&gt;Label=&lt;/span&gt;&lt;span class="s"&gt;"ContainerAppCommand Assignment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ContainerAppCommand&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"/opt/startup/start_nonappservice.sh"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This node specifies the command to run when the container starts. This is the same as the &lt;code&gt;Path&lt;/code&gt; value in the &lt;code&gt;Docker Desktop&lt;/code&gt; screenshot. Now, you can build the container image with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet publish ./FunctionAppWithMSBuild &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-t&lt;/span&gt;:PublishContainer &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--os&lt;/span&gt; linux &lt;span class="nt"&gt;--arch&lt;/span&gt; x64 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt;:ContainerBaseImage&lt;span class="o"&gt;=&lt;/span&gt;mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt;:ContainerRepository&lt;span class="o"&gt;=&lt;/span&gt;funcapp &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt;:ContainerImageTag&lt;span class="o"&gt;=&lt;/span&gt;latest-msbuild &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt;:ContainerWorkingDirectory&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/home/site/wwwroot"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can set the base container image with &lt;code&gt;mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-slim&lt;/code&gt; or &lt;code&gt;mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-mariner&lt;/code&gt;, depending on your requirements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This command takes four properties – &lt;code&gt;ContainerBaseImage&lt;/code&gt;, &lt;code&gt;ContainerRepository&lt;/code&gt;, &lt;code&gt;ContainerImageTag&lt;/code&gt;, and &lt;code&gt;ContainerWorkingDirectory&lt;/code&gt;. With this &lt;code&gt;dotnet publish&lt;/code&gt; command, you have the same development experience as building the container image with the &lt;code&gt;docker build&lt;/code&gt; command, without having to rely on &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run the following command to run the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 7071:80 &lt;span class="nt"&gt;--name&lt;/span&gt; funcappmsbuild funcapp:latest-msbuild
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your function app is now up and running in a container. Open the browser and navigate to &lt;code&gt;http://localhost:7071/api/HttpExampleTrigger&lt;/code&gt; to see the function app running.&lt;/p&gt;

&lt;p&gt;Open your Docker Desktop and check the running container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F08%2Fcontainerising-azure-functions-without-dockerfile-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2024%2F08%2Fcontainerising-azure-functions-without-dockerfile-02.png" alt="Container running Azure Functions app #2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you see the &lt;code&gt;Path&lt;/code&gt; and &lt;code&gt;Args&lt;/code&gt; value? The &lt;code&gt;Path&lt;/code&gt; value is &lt;code&gt;/opt/startup/start_nonappservice.sh&lt;/code&gt; and the &lt;code&gt;Args&lt;/code&gt; value has &lt;code&gt;dotnet&lt;/code&gt; and &lt;code&gt;FunctionAppWithMSBuild.dll&lt;/code&gt;. But these arguments are ignored because the shell script doesn't take any argument.&lt;/p&gt;

&lt;p&gt;Once you're done, stop and remove the container with the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop funcappmsbuild &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;rm &lt;/span&gt;funcappmsbuild
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you've got the Azure Functions app containerised without &lt;code&gt;Dockerfile&lt;/code&gt;. With this &lt;code&gt;dotnet publish&lt;/code&gt; approach, you can easily change the base container image, repository, tag, and working directory without relying on &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;So far, I've walked through how .NET developers can containerise their .NET-based Azure Functions apps with two different options. You can either write &lt;code&gt;Dockerfile&lt;/code&gt;s or use &lt;code&gt;dotnet publish&lt;/code&gt; to build container images. Which one do you prefer?&lt;/p&gt;

&lt;h2&gt;
  
  
  More about MSBuild for Containers?
&lt;/h2&gt;

&lt;p&gt;If you want to learn more options about containers with MSBuild, the following links might be helpful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;.NET Aspire&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/core/docker/publish-as-container?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;Containerise a .NET app with &lt;code&gt;dotnet publish&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/core/docker/container-images?WT.mc_id=dotnet-147943-juyoo" rel="noopener noreferrer"&gt;.NET container images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>containers</category>
      <category>docker</category>
      <category>azurefunctions</category>
    </item>
    <item>
      <title>Different Containerising Options for .NET Developers</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Mon, 12 Aug 2024 13:00:59 +0000</pubDate>
      <link>https://forem.com/dotnet/different-containerising-options-for-net-developers-cne</link>
      <guid>https://forem.com/dotnet/different-containerising-options-for-net-developers-cne</guid>
      <description>&lt;p&gt;As a .NET developer, if you want to containerise your .NET apps, you know you need a &lt;code&gt;Dockerfile&lt;/code&gt; first. &lt;code&gt;Dockerfile&lt;/code&gt; is a spec that describes which OS is used, where the .NET code is located, how the .NET code is compiled and how the .NET app is executed. Once your Dockerfile is ready, you can build your container image. But is the Dockerfile the only option for building your .NET apps in a container? How about the container orchestration? Is there any way to orchestrate containers other than running the &lt;code&gt;docker compose up&lt;/code&gt; command? Throughout this post, I'm going to discuss which options are available for .NET developers to containerise your .NET apps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find a sample code from:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/devkimchi" rel="noopener noreferrer"&gt;
        devkimchi
      &lt;/a&gt; / &lt;a href="https://github.com/devkimchi/msbuild-for-containers" rel="noopener noreferrer"&gt;
        msbuild-for-containers
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This provides sample .NET apps using container images with Dockerfile, with MSBuild, and with .NET Aspire support on Docker Desktop.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;MSBuild for Containers&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;As a .NET developer, when you build a container image for your app, you can use the &lt;code&gt;Dockerfile&lt;/code&gt; to define the container image. However, you can also use the &lt;code&gt;dotnet publish&lt;/code&gt; command to build and publish the container image without a &lt;code&gt;Dockerfile&lt;/code&gt;. This repository provides sample .NET apps using container images with &lt;code&gt;Dockerfile&lt;/code&gt; and with &lt;code&gt;dotnet publish&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In addition to that, if you want to orchestrate containers Docker Compose is usually the first approach. However, you can also use the .NET Aspire to generate the Docker Compose file from the .NET Aspire manifest JSON file. This repository also provides a sample .NET app using the .NET Aspire to orchestrate containers.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/download/dotnet/8.0?WT.mc_id=dotnet-144884-juyoo" rel="nofollow noopener noreferrer"&gt;.NET SDK 8.0+&lt;/a&gt; with &lt;a href="https://learn.microsoft.com/dotnet/aspire/fundamentals/setup-tooling?WT.mc_id=dotnet-144884-juyoo" rel="nofollow noopener noreferrer"&gt;.NET Aspire workload&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://visualstudio.microsoft.com/?WT.mc_id=dotnet-144884-juyoo" rel="nofollow noopener noreferrer"&gt;Visual Studio&lt;/a&gt; or &lt;a href="https://code.visualstudio.com/?WT.mc_id=dotnet-144884-juyoo" rel="nofollow noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; + &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit" rel="nofollow noopener noreferrer"&gt;C# Dev Kit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/prom3theu5/aspirational-manifests" rel="noopener noreferrer"&gt;Aspirate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/products/docker-desktop" rel="nofollow noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Run with &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;docker init&lt;/code&gt; to create a new Dockerfile for…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/devkimchi/msbuild-for-containers" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;There are a few prerequisites to containerise .NET apps effectively.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/download/dotnet/8.0?WT.mc_id=dotnet-147226-juyoo" rel="noopener noreferrer"&gt;.NET SDK 8.0+&lt;/a&gt; with &lt;a href="https://learn.microsoft.com/dotnet/aspire/fundamentals/setup-tooling?WT.mc_id=dotnet-147226-juyoo" rel="noopener noreferrer"&gt;.NET Aspire workload&lt;/a&gt; and &lt;a href="https://github.com/prom3theu5/aspirational-manifests" rel="noopener noreferrer"&gt;Aspirate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Containerise with &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In the sample code repository, there are two .NET apps, &lt;code&gt;ApiApp&lt;/code&gt; and &lt;code&gt;WebApp&lt;/code&gt;. When you run both apps locally, it communicates with each other by running the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/MSBuildForContainers.ApiApp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/MSBuildForContainers.WebApp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you want to containerise both apps. How can you do that? The first option is to write a &lt;code&gt;Dockerfile&lt;/code&gt; for each app. You can either manually write &lt;code&gt;Dockerfile&lt;/code&gt; or run the command, &lt;code&gt;docker init&lt;/code&gt;. Once you have &lt;code&gt;Dockerfile&lt;/code&gt; files for both apps, run the following commands to build the container images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For ApiApp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;apiapp:latest&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# For WebApp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webapp:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We all know how &lt;code&gt;Dockerfile&lt;/code&gt; is useful to build container images. However, &lt;code&gt;Dockerfile&lt;/code&gt; is yet another code and should be maintained. If you have many apps to containerise, you need to write &lt;code&gt;Dockerfile&lt;/code&gt; files respectively. This is a bit cumbersome. Is there any other way to containerise .NET apps, without writing &lt;code&gt;Dockerfile&lt;/code&gt; files?&lt;/p&gt;

&lt;h2&gt;
  
  
  Containerise with &lt;code&gt;dotnet publish&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;MSBuild supports containerisation by itself. It uses the &lt;code&gt;dotnet publish&lt;/code&gt; command to build the container image for each app. To do this, you might need to update your &lt;code&gt;.csproj&lt;/code&gt; file to include the containerisation settings. The following is the &lt;code&gt;MSBuildForContainers.ApiApp.csproj&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ContainerRepository&amp;gt;&lt;/span&gt;apiapp&lt;span class="nt"&gt;&amp;lt;/ContainerRepository&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ContainerImageTag&amp;gt;&lt;/span&gt;latest&lt;span class="nt"&gt;&amp;lt;/ContainerImageTag&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two properties replaces the &lt;code&gt;--tag&lt;/code&gt; option in the &lt;code&gt;docker build&lt;/code&gt; command. Once you have these properties in the &lt;code&gt;.csproj&lt;/code&gt; file, run the following command. Note that the &lt;code&gt;-t:PublishContainer&lt;/code&gt; indicates the containerisation and the &lt;code&gt;--os&lt;/code&gt; and &lt;code&gt;--arch&lt;/code&gt; options specify the target OS and architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/MSBuildForContainers.ApiApp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;PublishContainer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--arch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you'll have the container image. If you want to change the base image to the &lt;a href="https://devblogs.microsoft.com/dotnet/announcing-dotnet-chiseled-containers/?WT.mc_id=dotnet-147226-juyoo" rel="noopener noreferrer"&gt;chiseled container one&lt;/a&gt;, add another property to the &lt;code&gt;.csproj&lt;/code&gt; file. The following property sets the chiseled Ubuntu 24.04 image as the base image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ContainerBaseImage&amp;gt;&lt;/span&gt;mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled&lt;span class="nt"&gt;&amp;lt;/ContainerBaseImage&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run the &lt;code&gt;dotnet publish&lt;/code&gt; command above again. This time, the container image is built based on the chiseled image. Let's do the same thing against the &lt;code&gt;MSBuildForContainers.WebApp.csproj&lt;/code&gt; file. This way, you can containerise your .NET apps without writing &lt;code&gt;Dockerfile&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Alternatively, you don't even need those properties in the &lt;code&gt;.csproj&lt;/code&gt; file. You can run the following command to build the container image directly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/MSBuildForContainers.ApiApp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;PublishContainer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--arch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ContainerBaseImage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ContainerRepository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;apiapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ContainerImageTag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have the same development experience as building the container image with the &lt;code&gt;docker build&lt;/code&gt; command, without having to rely on &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, you've got chiseled container images for both API app and web app. How to let them talk to each other?&lt;/p&gt;

&lt;h2&gt;
  
  
  Container orchestration with &lt;code&gt;docker compose&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The existing web app container image can't talk to the API app container image because it doesn't know where the API app container is. To make the web app container talk to the API app container, you need to slightly modify the web app and build the container image again. Open the &lt;code&gt;MSBuildForContainers.WebApp/Program.cs&lt;/code&gt; file and update the base address value as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IApiAppClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ApiAppClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://localhost:5051/"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IApiAppClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ApiAppClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://apiapp:8080/"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the &lt;code&gt;dotnet publish&lt;/code&gt; command again to build the web app container image. Now, you can manually run the &lt;code&gt;docker run&lt;/code&gt; command to run both containers by attaching the same network. But for the container orchestration, the &lt;code&gt;docker compose up&lt;/code&gt; command is much easier to use. The &lt;code&gt;docker-compose.yml&lt;/code&gt; file is already prepared in the sample code repository. Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/docker-compose.yaml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, both containers are up and running. The web app container can talk to the API app container. This is how you can orchestrate containers with &lt;code&gt;docker compose up&lt;/code&gt;. Again, this Docker Compose file is yet another code and should be maintained. If you have many apps to orchestrate, how would you manage them?&lt;/p&gt;

&lt;h2&gt;
  
  
  Container orchestration with .NET Aspire and Aspirate
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview?WT.mc_id=dotnet-147226-juyoo" rel="noopener noreferrer"&gt;.NET Aspire&lt;/a&gt; is to orchestrate .NET apps in containers. For this Docker Compose orchestration purpose, &lt;a href="https://github.com/prom3theu5/aspirational-manifests" rel="noopener noreferrer"&gt;Aspirate&lt;/a&gt; is used, which is a tool that generates the Docker Compose file, a &lt;a href="https://kustomize.io/" rel="noopener noreferrer"&gt;Kustomize file&lt;/a&gt; or &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;helm file&lt;/a&gt; for the container orchestration. To use Aspirate, you need to install it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;aspirate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the sample code repository, Both web app and API app are orchestrated with .NET Aspire in another branch. Switch to the &lt;code&gt;aspire&lt;/code&gt; branch with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;aspire&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's generate the Docker Compose file with Aspirate. Run the following command to generate the .NET Aspire manifest file first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/MSBuildForContainers.AppHost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--publisher&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--output-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;/aspire-manifest.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, generate the Docker Compose file with the Aspirate command. Note that it intentionally excludes the .NET Aspire dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;aspirate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;generate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--project-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/MSBuildForContainers.AppHost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--aspire-manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/aspire-manifest.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--output-format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--disable-secrets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--include-dashboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you have the Docker Compose file generated. Run the &lt;code&gt;docker compose up&lt;/code&gt; command to orchestrate the containers. This way, you can orchestrate your .NET apps in containers without manually writing the Docker Compose file.&lt;/p&gt;




&lt;p&gt;So far, I've walked through how .NET developers can containerise their .NET apps with different options. You can either write &lt;code&gt;Dockerfile&lt;/code&gt; files or use &lt;code&gt;dotnet publish&lt;/code&gt; to build container images. You can orchestrate containers by writing the Docker Compose file by hand or letting .NET Aspire and Aspirate generate it automatically. Which one do you prefer?&lt;/p&gt;

&lt;h2&gt;
  
  
  More about MSBuild for Containers?
&lt;/h2&gt;

&lt;p&gt;If you want to learn more options about containers with MSBuild, the following links might be helpful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview?WT.mc_id=dotnet-147226-juyoo" rel="noopener noreferrer"&gt;.NET Aspire&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/core/docker/publish-as-container?WT.mc_id=dotnet-147226-juyoo" rel="noopener noreferrer"&gt;Containerise a .NET app with &lt;code&gt;dotnet publish&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/core/docker/container-images?WT.mc_id=dotnet-147226-juyoo" rel="noopener noreferrer"&gt;.NET container images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>containers</category>
      <category>docker</category>
      <category>msbuild</category>
    </item>
    <item>
      <title>DevContainers for Azure and .NET</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Thu, 13 Oct 2022 21:43:53 +0000</pubDate>
      <link>https://forem.com/azure/devcontainers-for-azure-and-net-5942</link>
      <guid>https://forem.com/azure/devcontainers-for-azure-and-net-5942</guid>
      <description>&lt;p&gt;Containers are everywhere. It's not just to make both the development and production environment consistent but also to build CI/CD pipelines, test automations, and so on. While everyone does their own way of building containers, it has become the maintenance job.&lt;/p&gt;

&lt;p&gt;Maintenance will become easier if it uses standardised ways. What if there is an open standard spec for it? Actually, there is. It is called &lt;a href="https://containers.dev/" rel="noopener noreferrer"&gt;Development Container or DevContainer&lt;/a&gt;. You can build the container and store it in your GitHub repository with this spec. Then, &lt;a href="https://docs.github.com/codespaces/overview" rel="noopener noreferrer"&gt;GitHub Codespaces&lt;/a&gt; makes use of it. At least a project using the same GitHub repository uses the same development environment. Throughout this post, I'm going to introduce the DevContainer spec and discuss how it's used for Azure and .NET app development.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can download the DevContainer template from this GitHub repository, which is used for this post.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/devkimchi" rel="noopener noreferrer"&gt;
        devkimchi
      &lt;/a&gt; / &lt;a href="https://github.com/devkimchi/devcontainers-dotnet" rel="noopener noreferrer"&gt;
        devcontainers-dotnet
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This is the template repository that contains the devcontainer settings for .NET app development
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;devcontainers for .NET&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This is the template repository that contains the devcontainer settings for .NET app development.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;If you want to use this devcontainer settings, you can create a new repository with this template repository, by clicking the "&lt;em&gt;Use this template&lt;/em&gt;" button.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/devkimchi/devcontainers-dotnet./images/use-this-template.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fdevkimchi%2Fdevcontainers-dotnet.%2Fimages%2Fuse-this-template.png" alt="Use this template"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Options – &lt;code&gt;devcontainer.json&lt;/code&gt;
&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Base Image&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;By default, this devcontainer settings uses the base image of Ubuntu 22.04 LTS (jammy).&lt;/p&gt;
&lt;div class="highlight highlight-source-js notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-s"&gt;"build"&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-s"&gt;"dockerfile"&lt;/span&gt;: &lt;span class="pl-s"&gt;"./Dockerfile"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-s"&gt;"context"&lt;/span&gt;: &lt;span class="pl-s"&gt;"."&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-s"&gt;"args"&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-s"&gt;"VARIANT"&lt;/span&gt;: &lt;span class="pl-s"&gt;"6.0-jammy"&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;However, there is currently a bug on the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp" rel="nofollow noopener noreferrer"&gt;C# extension v1.25.0&lt;/a&gt; for Razor support on Ubuntu 22.04 LTS (jammy). Therefore, if you need Razor support, build your devcontainer with Ubuntu 20.04 LTS (focal).&lt;/p&gt;
&lt;div class="highlight highlight-source-js notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-s"&gt;"build"&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-s"&gt;"dockerfile"&lt;/span&gt;: &lt;span class="pl-s"&gt;"./Dockerfile"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-s"&gt;"context"&lt;/span&gt;: &lt;span class="pl-s"&gt;"."&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-s"&gt;"args"&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c"&gt;// Use this only if you need Razor support, until OmniSharp supports .NET 6 properly&lt;/span&gt;
    &lt;span class="pl-s"&gt;"VARIANT"&lt;/span&gt;: &lt;span class="pl-s"&gt;"6.0-focal"&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/devkimchi/devcontainers-dotnet" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Elements of DevContainer
&lt;/h2&gt;

&lt;p&gt;All you need for building a DevContainer are those two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;devcontainer.json&lt;/code&gt;: It defines all the metadata details to build a DevContainer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile&lt;/code&gt;: It defines the Docker container image that is invoked within &lt;code&gt;devcontainer.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optionally, you can call a shell script during the container building lifecycle. The &lt;code&gt;postCreateCommand&lt;/code&gt; attribute of the &lt;code&gt;devcontainer.json&lt;/code&gt; file takes care of it. For example, the attribute executes the &lt;code&gt;post-create.sh&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Alternatively, you can invoke the &lt;code&gt;RUN&lt;/code&gt; command within &lt;code&gt;Dockerfile&lt;/code&gt; for the shell script execution. In this case, you're running the script with the &lt;code&gt;root&lt;/code&gt; permissions. On the other hand, &lt;code&gt;postCreateCommand&lt;/code&gt; runs the script with the user privileges declared in &lt;code&gt;devcontainer.json&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Structure of &lt;code&gt;devcontainer.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;devcontainer.json&lt;/code&gt; consists of various sections for metadata declarations. I only deal with a small portion of it in this post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;build&lt;/code&gt;: This section is for the Docker container build – location of &lt;code&gt;Dockerfile&lt;/code&gt;, parameters passed to it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;forwardPorts&lt;/code&gt;: This section defines the list of port numbers to expose. For example, &lt;code&gt;7071&lt;/code&gt; is for Azure Functions, &lt;code&gt;5000&lt;/code&gt; and &lt;code&gt;5001&lt;/code&gt; are for ASP.NET Web/API apps, and &lt;code&gt;4280&lt;/code&gt; is for Azure Static Web App development.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features&lt;/code&gt;: While you can add everything in &lt;code&gt;Dockerfile&lt;/code&gt; for the build, there are already pre-configured &lt;a href="https://github.com/devcontainers/features" rel="noopener noreferrer"&gt;features&lt;/a&gt; you can optionally add. You can find the complete list of the features at &lt;a href="https://github.com/devcontainers/features/tree/main/src" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Some examples of those features are common utilities and tools like Azure CLI, GitHub CLI and Terraform, and languages like node.js, Java, .NET, Python, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customisations&lt;/code&gt;: This section is responsible for the tools used in DevContainer. One of the tools is VS Code which the &lt;code&gt;vscode&lt;/code&gt; attribute represents. It defines which extensions need to be included (&lt;code&gt;extensions&lt;/code&gt;) and how the editor behaves (&lt;code&gt;settings&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remoteUser&lt;/code&gt;: This sets the user account within DevContainer. Unless it is set, DevContainer runs as the &lt;code&gt;root&lt;/code&gt; account. In this post, we use &lt;code&gt;vscode&lt;/code&gt; as the non-root account.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postCreateCommand&lt;/code&gt;: Once DevContainer is created, run additional commands with the remote-user account privileges. Through this attribute, you can run an additional shell script.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;There are more attributes than the ones mentioned above. If you want to know more, visit &lt;a href="https://github.com/devcontainers/spec" rel="noopener noreferrer"&gt;this page&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Order of Building DevContainer
&lt;/h2&gt;

&lt;p&gt;How does DevContainer get built? Here is the rough order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the Docker container. If you add the shell script through the &lt;code&gt;RUN&lt;/code&gt; command in &lt;code&gt;Dockerfile&lt;/code&gt;, the shell script is run this time.&lt;/li&gt;
&lt;li&gt;Run features declared in the &lt;code&gt;features&lt;/code&gt; section of &lt;code&gt;devcontainer.json&lt;/code&gt; while building the Docker container.&lt;/li&gt;
&lt;li&gt;Run commands declared in the &lt;code&gt;postCreateCommand&lt;/code&gt; attribute of &lt;code&gt;devcontainer.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Apply &lt;a href="https://docs.github.com/codespaces/customizing-your-codespace/personalizing-github-codespaces-for-your-account" rel="noopener noreferrer"&gt;dotfiles&lt;/a&gt; after &lt;code&gt;postCreateCommand&lt;/code&gt;, if you have it.&lt;/li&gt;
&lt;li&gt;Apply both &lt;code&gt;extensions&lt;/code&gt; and &lt;code&gt;settings&lt;/code&gt; of &lt;code&gt;devcontainer.json&lt;/code&gt; at the startup of the DevContainer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By understanding the order above, let's build the DevContainer for .NET app development on Azure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base Container Image
&lt;/h2&gt;

&lt;p&gt;When you visit the &lt;a href="https://github.com/devcontainers/images" rel="noopener noreferrer"&gt;repository for DevContainer images&lt;/a&gt;, there are pre-configured &lt;a href="https://github.com/devcontainers/images/tree/main/src" rel="noopener noreferrer"&gt;list of base Docker container images for DevContainer&lt;/a&gt;. Let's choose the one for .NET. You can choose many different variants, but either &lt;code&gt;6.0-jammy&lt;/code&gt; (Ubuntu 22.04) or &lt;code&gt;6.0-focal&lt;/code&gt; (Ubuntu 20.04) would be yours if you prefer using Ubuntu-based images with .NET 6. Ubuntu 22.04 is set to the default base image in this post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# [Choice] .NET version: 6.0-jammy, 6.0-focal&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; VARIANT="6.0-jammy"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/sdk:${VARIANT}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;devcontainer.json&lt;/code&gt; Configuration
&lt;/h2&gt;

&lt;p&gt;Let's configure &lt;code&gt;devcontainer.json&lt;/code&gt; containing all the metadata details.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;build&lt;/code&gt; Section
&lt;/h3&gt;

&lt;p&gt;It declares the location of &lt;code&gt;Dockerfile&lt;/code&gt; and sends parameters to it. In this case, use &lt;code&gt;6.0-jammy&lt;/code&gt; for &lt;code&gt;VARIANT&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dockerfile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./Dockerfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"VARIANT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6.0-jammy"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// Use this only if you need Razor support, until OmniSharp supports .NET 6 properly&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// "VARIANT": "6.0-focal"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you are building a Blazor app, pass &lt;code&gt;6.0-focal&lt;/code&gt; (Ubuntu 20.04) instead of &lt;code&gt;6.0-jammy&lt;/code&gt; (Ubuntu 22.04). It's because the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp&amp;amp;WT.mc_id=dotnet-78728-juyoo" rel="noopener noreferrer"&gt;C# extension&lt;/a&gt; has a &lt;a href="https://github.com/OmniSharp/omnisharp-vscode/issues/5347" rel="noopener noreferrer"&gt;bug&lt;/a&gt; on Ubuntu 22.04.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;forwardPorts&lt;/code&gt; Section
&lt;/h3&gt;

&lt;p&gt;You might need to expose some specific port numbers – &lt;code&gt;7071&lt;/code&gt; for Azure Functions, &lt;code&gt;5000&lt;/code&gt; and &lt;code&gt;5001&lt;/code&gt; for ASP.NET Web/API apps, and/or &lt;code&gt;4280&lt;/code&gt; for Azure Static Web Apps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"forwardPorts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Azure Functions&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;7071&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// ASP.NET Core Web/API App, Blazor App&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Azure Static Web App CLI&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="mi"&gt;4280&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;features&lt;/code&gt; Section
&lt;/h3&gt;

&lt;p&gt;On top of the base image, if you want to add more tools and/or languages, add them to the &lt;code&gt;features&lt;/code&gt; section.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Common Utils&lt;/strong&gt;: You can add zsh and oh-my-zsh through this feature.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Install common utilities&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ghcr.io/devcontainers/features/common-utils:1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"installZsh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"installOhMyZsh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"upgradePackages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1000"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure CLI&lt;/strong&gt;: You can add Azure CLI through this feature.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Uncomment the below to install Azure CLI&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ghcr.io/devcontainers/features/azure-cli:1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GitHub CLI&lt;/strong&gt;: You can add GitHub CLI through this feature.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Uncomment the below to install GitHub CLI&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ghcr.io/devcontainers/features/github-cli:1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;node.js&lt;/strong&gt;: You can add the latest LTS version of node.js through this feature.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Uncomment the below to install node.js&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ghcr.io/devcontainers/features/node:1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nodeGypDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nvmInstallPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/share/nvm"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suppose you have another feature, but it doesn't exist yet in &lt;a href="https://github.com/devcontainers/features/tree/main/src" rel="noopener noreferrer"&gt;this features list&lt;/a&gt;. In that case, you can manually add it through the &lt;code&gt;postCreateCommand&lt;/code&gt; attribute by invoking &lt;code&gt;post-create.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do you want to add those features in order? Then use this &lt;code&gt;overrideFeatureInstallOrder&lt;/code&gt; attribute. Here in this post, the &lt;code&gt;common-utils&lt;/code&gt; feature runs first, and then the rest features are installed in random order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"overrideFeatureInstallOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"ghcr.io/devcontainers/features/common-utils"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;customizations.vscode.extensions&lt;/code&gt; Section
&lt;/h3&gt;

&lt;p&gt;You might have some extensions automatically installed while creating the DevContainer. The &lt;code&gt;customisations.vscode.extensions&lt;/code&gt; section holds all the extensions you want to install. The list of extensions below is for Azure and .NET app development. If you want to add more, search them on &lt;a href="https://marketplace.visualstudio.com/VSCode?WT.mc_id=dotnet-78728-juyoo" rel="noopener noreferrer"&gt;Visual Studio Code Marketplace&lt;/a&gt; and add their extension ID. For example, the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp&amp;amp;WT.mc_id=dotnet-78728-juyoo" rel="noopener noreferrer"&gt;C# extension&lt;/a&gt; has its extension ID of &lt;code&gt;ms-dotnettools.csharp&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"customizations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vscode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// Recommended extensions - GitHub&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"cschleiden.vscode-github-actions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"GitHub.vscode-pull-request-github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="c1"&gt;// Recommended extensions - Azure&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"ms-azuretools.vscode-bicep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="c1"&gt;// Recommended extensions - Collaboration&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"eamodio.gitlens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"EditorConfig.EditorConfig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"MS-vsliveshare.vsliveshare-pack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"streetsidesoftware.code-spell-checker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="c1"&gt;// Recommended extensions - .NET&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Fudge.auto-using"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"jongrant.csharpsortusings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"kreativ-software.csharpextensions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="c1"&gt;// Recommended extensions - Power Platform&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"microsoft-IsvExpTools.powerplatform-vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="c1"&gt;// Recommended extensions - Markdown&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"bierner.github-markdown-preview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"DavidAnson.vscode-markdownlint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"docsmsft.docs-linting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"johnpapa.read-time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"yzhang.markdown-all-in-one"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="c1"&gt;// Required extensions&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"GitHub.copilot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"ms-dotnettools.csharp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"ms-vscode.PowerShell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"ms-vscode.vscode-node-azure-pack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"VisualStudioExptTeam.vscodeintellicode"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;customizations.vscode.settings&lt;/code&gt; Section
&lt;/h3&gt;

&lt;p&gt;You might also want to personalise your editor settings while creating the DevContainer. You can set them on the &lt;code&gt;customizations.vscode.settings&lt;/code&gt; section. The list of settings below are examples of personalised settings. If you want to get them more personalised, refer to the &lt;a href="https://code.visualstudio.com/docs/getstarted/settings?WT.mc_id=dotnet-78728-juyoo" rel="noopener noreferrer"&gt;User and Workspace Settings&lt;/a&gt; page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;bash&lt;/code&gt; shell is the default terminal. If you want to change its default behaviour to &lt;code&gt;zsh&lt;/code&gt;, use these settings.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"customizations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vscode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// Uncomment if you want to use zsh as the default shell&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"terminal.integrated.defaultProfile.linux"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zsh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"terminal.integrated.profiles.linux"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"zsh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/zsh"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want to change your terminal font, use this setting. It's specifically for oh-my-zsh or oh-my-posh.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"customizations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vscode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// Uncomment if you want to use CaskaydiaCove Nerd Font as the default terminal font&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"terminal.integrated.fontFamily"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CaskaydiaCove Nerd Font"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want to disable the minimap feature, use this setting.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"customizations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vscode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// Uncomment if you want to disable the minimap view&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"editor.minimap.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want to change the behaviour of the explorer, use these settings. All files are sorted by extension and nested by relevant files.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"customizations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vscode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// Recommended settings for the explorer pane&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"explorer.sortOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"explorer.fileNesting.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"explorer.fileNesting.patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"*.bicep"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${capture}.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"*.razor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${capture}.razor.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"*.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${capture}.js.map"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;postCreateCommand&lt;/code&gt; Section
&lt;/h3&gt;

&lt;p&gt;Once your DevContainer is created, you might want to do something more. For example, you can't add an extra feature through the &lt;code&gt;features&lt;/code&gt; section because it's not ready. However, executing a shell script can add those additional features through this &lt;code&gt;postCreateCommand&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;Here's the one for running &lt;code&gt;post-create.sh&lt;/code&gt; through the &lt;code&gt;bash&lt;/code&gt; shell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"postCreateCommand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/bin/bash ./.devcontainer/post-create.sh &amp;gt; ~/post-create.log"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the one for running &lt;code&gt;post-create.sh&lt;/code&gt; through the &lt;code&gt;zsh&lt;/code&gt; shell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"postCreateCommand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/zsh ./.devcontainer/post-create.sh &amp;gt; ~/post-create.log"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a look at what happens within &lt;code&gt;post-create.sh&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;post-create.sh&lt;/code&gt; Execution
&lt;/h2&gt;

&lt;p&gt;This &lt;code&gt;post-create.sh&lt;/code&gt; is run right after the DevContainer is created.&lt;/p&gt;

&lt;h3&gt;
  
  
  CaskaydiaCove Nerd Font
&lt;/h3&gt;

&lt;p&gt;It's a good idea to install a custom font, if you use either oh-my-zsh or oh-my-posh for your terminal. The following script is to install CaskaydiaCove Nerd Font.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## CaskaydiaCove Nerd Font&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to install the CaskaydiaCove Nerd Font&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.local/share&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.local/share/fonts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/ryanoasis/nerd-fonts/releases/latest/download/CascadiaCode.zip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;unzip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CascadiaCode.zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.local/share/fonts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CascadiaCode.zip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure CLI Extensions
&lt;/h3&gt;

&lt;p&gt;You can run this script if you install Azure CLI through the &lt;code&gt;feature&lt;/code&gt; section of &lt;code&gt;devcontainer.json&lt;/code&gt;. However, because it adds ALL extensions, it takes about 30-60 mins, depending on your network latency. Therefore be careful to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## AZURE CLI EXTENSIONS ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to install Azure CLI extensions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list-available&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[].name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.[]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$extensions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$extension&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can use &lt;code&gt;extensions=(list of selected extensions you want)&lt;/code&gt;, instead of using &lt;code&gt;extensions=$(az extension list-available --query "[].name" | jq -c -r '.[]')&lt;/code&gt;. For example, I use &lt;code&gt;extensions=(account alias deploy-to-azure functionapp subscription webapp)&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Azure Bicep CLI
&lt;/h3&gt;

&lt;p&gt;If you use Azure Bicep, you can run this script to install Bicep CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## AZURE BICEP CLI ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to install Azure Bicep CLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bicep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure Functions Core Tools
&lt;/h3&gt;

&lt;p&gt;Do you develop Azure Functions apps? Then activate this script. As it installs through npm, you should install the node.js feature through the &lt;code&gt;features&lt;/code&gt; section of &lt;code&gt;devcontainer.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## AZURE FUNCTIONS CORE TOOLS ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to install Azure Functions Core Tools&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;azure-functions-core-tools&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--unsafe-perm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure Static Web Apps CLI
&lt;/h3&gt;

&lt;p&gt;Unlock this script if you're building a Blazor WASM app and deploying it to Azure Static Web Apps. Like Azure Functions Core Tools, it relies on the node.js feature through the &lt;code&gt;features&lt;/code&gt; section of &lt;code&gt;devcontainer.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## AZURE STATIC WEB APPS CLI ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to install Azure Static Web Apps CLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;azure/static-web-apps-cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure Dev CLI
&lt;/h3&gt;

&lt;p&gt;If you want to activate the Azure Dev CLI, run the following script. Azure Dev CLI needs both Azure CLI and GitHub CLI as dependencies. Therefore, make sure you have already installed them through the &lt;code&gt;features&lt;/code&gt; section of &lt;code&gt;devcontainer.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## AZURE DEV CLI ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to install Azure Dev CLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-fsSL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://aka.ms/install-azd.sh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  oh-my-zsh – Plugins and Themes
&lt;/h3&gt;

&lt;p&gt;There are a bunch of plugins and themes for oh-my-zsh. You can follow the pattern below. Here are some recommended plugins and theme – powerlevel10k.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## OH-MY-ZSH PLUGINS &amp;amp; THEMES (POWERLEVEL10K) ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to install oh-my-zsh plugins and themes (powerlevel10k) without dotfiles integration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/zsh-users/zsh-completions.git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.oh-my-zsh/custom/plugins/zsh-completions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/zsh-users/zsh-syntax-highlighting.git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/zsh-users/zsh-autosuggestions.git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.oh-my-zsh/custom/plugins/zsh-autosuggestions&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/romkatv/powerlevel10k.git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.oh-my-zsh/custom/themes/powerlevel10k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.oh-my-zsh/custom/themes/powerlevel10k/powerlevel10k.zsh-theme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.oh-my-zsh/custom/themes/powerlevel10k.zsh-theme&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  oh-my-zsh – powerlevel10k Theme Configurations
&lt;/h3&gt;

&lt;p&gt;powerlevel10k has its settings file, called &lt;code&gt;p10k.zsh&lt;/code&gt;. I've got my own &lt;code&gt;p10k.zsh&lt;/code&gt; settings – with a clock and without the clock. Activate the script below to copy them to DevContainer. You don't need to run the following script if you have your own ones from your &lt;code&gt;dotfiles&lt;/code&gt; repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## OH-MY-ZSH - POWERLEVEL10K SETTINGS ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to update the oh-my-zsh settings without dotfiles integration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/justinyoo/devcontainers-dotnet/main/oh-my-zsh/.p10k-with-clock.zsh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.p10k-with-clock.zsh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/justinyoo/devcontainers-dotnet/main/oh-my-zsh/.p10k-without-clock.zsh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.p10k-without-clock.zsh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/justinyoo/devcontainers-dotnet/main/oh-my-zsh/switch-p10k-clock.sh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/switch-p10k-clock.sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;chmod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;~/switch-p10k-clock.sh&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.p10k-with-clock.zsh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.p10k.zsh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc.bak&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;awk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'{gsub(/ZSH_THEME=\"codespaces\"/, "ZSH_THEME=\"powerlevel10k\"")}1'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc.replaced&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc.replaced&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;awk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'{gsub(/plugins=\(git\)/, "plugins=(git zsh-completions zsh-syntax-highlighting zsh-autosuggestions)")}1'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc.replaced&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc.replaced&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"
# To customize prompt, run 'p10k configure' or edit ~/.p10k.zsh.
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.zshrc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  oh-my-posh – Installation
&lt;/h3&gt;

&lt;p&gt;Are you into PowerShell but also want to use something like oh-my-zsh? Then oh-my-posh is your friend. Run the following script to install.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## OH-MY-POSH ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to install oh-my-posh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/posh-linux-amd64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-O&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/usr/local/bin/oh-my-posh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chmod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/usr/local/bin/oh-my-posh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  oh-my-posh – Configurations
&lt;/h3&gt;

&lt;p&gt;oh-my-posh has its own powerlevel10k configurations. Run the following script to copy those settings files to your DevContainer. Again, if you've set them on your &lt;code&gt;dotfiles&lt;/code&gt; repository, you don't need to run the script below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;## OH-MY-POSH - POWERLEVEL10K SETTINGS ##&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Uncomment the below to update the oh-my-posh settings without dotfiles integration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/justinyoo/devcontainers-dotnet/main/oh-my-posh/p10k-with-clock.omp.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/p10k-with-clock.omp.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/justinyoo/devcontainers-dotnet/main/oh-my-posh/p10k-without-clock.omp.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/p10k-without-clock.omp.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/justinyoo/devcontainers-dotnet/main/oh-my-posh/switch-p10k-clock.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/switch-p10k-clock.ps1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.config/powershell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/justinyoo/devcontainers-dotnet/main/oh-my-posh/Microsoft.PowerShell_profile.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/.config/powershell/Microsoft.PowerShell_profile.ps1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/p10k-with-clock.omp.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="nx"&gt;/p10k.omp.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you complete the configuration like above, run your GitHub Codespaces instance. Then, you will see the terminal like below. On the left-hand side, it's the zsh shell applying oh-my-zsh. On the right-hand side, it's PowerShell using oh-my-posh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2022%2F10%2Fdevcontainers-for-dotnet-devs-on-azure-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevkimchi.com%2F2022%2F10%2Fdevcontainers-for-dotnet-devs-on-azure-01.png" alt="GH Codespaces" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;So far, we've discussed what the &lt;a href="https://containers.dev/" rel="noopener noreferrer"&gt;DevContainer&lt;/a&gt; is, how it is helpful for .NET app development on Azure, and what needs to be configured for DevContainer for .NET app development on Azure, using &lt;a href="https://docs.github.com/codespaces/overview" rel="noopener noreferrer"&gt;GitHub Codespaces&lt;/a&gt;. What has been discussed here in this post is the only small part of the DevContainer. Therefore, if you want to apply it to your or your team's development environment, you need to do more research by yourself. However, I hope this post gives at least some sort of insights for building DevContainers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to know more about DevContainers?
&lt;/h2&gt;

&lt;p&gt;There are documents and learning materials for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/shows/beginners-series-to-dev-containers/?WT.mc_id=dotnet-78728-juyoo" rel="noopener noreferrer"&gt;Beginner's Series to: Dev Containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/docs/remote/create-dev-container?WT.mc_id=dotnet-78728-juyoo" rel="noopener noreferrer"&gt;Create a development container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/codespaces/overview" rel="noopener noreferrer"&gt;GitHub Codespaces overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/codespaces/setting-up-your-project-for-codespaces/setting-up-your-project-for-codespaces?langId=dotnet" rel="noopener noreferrer"&gt;Add a dev container configuration to your repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>dotnet</category>
      <category>devcontainers</category>
      <category>codespaces</category>
    </item>
    <item>
      <title>Blazor WASM Custom 404 Page on GH Pages</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Fri, 07 Oct 2022 00:11:55 +0000</pubDate>
      <link>https://forem.com/dotnet/blazor-wasm-custom-404-page-on-gh-pages-1o99</link>
      <guid>https://forem.com/dotnet/blazor-wasm-custom-404-page-on-gh-pages-1o99</guid>
      <description>&lt;p&gt;It might be necessary to implement the custom 404 page while developing a &lt;a href="https://learn.microsoft.com/aspnet/core/blazor/?WT.mc_id=dotnet-77749-juyoo#blazor-webassembly" rel="noopener noreferrer"&gt;Blazor WebAssembly (WASM)&lt;/a&gt; app. But when you deploy your Blazor WASM app to &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;, it only shows GitHub's 404 page, not yours. Although there are many workarounds for this, throughout this post, I'm going to discuss how to use a Blazor web component for the 404 page on GitHub Pages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can download the sample application on this GitHub repository.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/fitability" rel="noopener noreferrer"&gt;
        fitability
      &lt;/a&gt; / &lt;a href="https://github.com/fitability/fitability.github.io" rel="noopener noreferrer"&gt;
        fitability.github.io
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      fitability™️ – your friendly fitness activity tracking app
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
    &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/fitability/.github/main/assets/github-repo-3840x1920.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ffitability%2F.github%2Fmain%2Fassets%2Fgithub-repo-3840x1920.png" width="240" height="120"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;fitability™️ – your friendly fitness activity tracking app&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;fitability™️ is an app that runs on your preferred platform – web, desktop or mobile – which traces your fitness activities, and shares them with your friends or on social media.&lt;/p&gt;
&lt;p&gt;It's all open-sourced under the MIT license.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Repositories&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/fitability/fitability-docs" rel="noopener noreferrer"&gt;fitability-docs&lt;/a&gt;: Documents&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fitability/fitability-app" rel="noopener noreferrer"&gt;fitability-app&lt;/a&gt;: Applications&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fitability/fitability-power" rel="noopener noreferrer"&gt;fitability-power&lt;/a&gt;: Power Apps app&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Credits&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Icons made by &lt;a href="https://www.flaticon.com/authors/freepik" rel="nofollow noopener noreferrer"&gt;Freepik&lt;/a&gt; from &lt;a href="https://www.flaticon.com/" rel="nofollow noopener noreferrer"&gt;www.flaticon.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/fitability/fitability.github.io" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Blazor WASM – Default 404 Page
&lt;/h2&gt;

&lt;p&gt;The default 404 page right after you create a Blazor WASM app project looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdevkimchi.com%2F2022%2F10%2F404-page-of-blazor-wasm-on-gh-pages-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdevkimchi.com%2F2022%2F10%2F404-page-of-blazor-wasm-on-gh-pages-01.png" alt="Default 404 page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's because &lt;code&gt;App.razor&lt;/code&gt; defines like that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* BlazorApp1.App.razor *@

&amp;lt;Router AppAssembly="@typeof(App).Assembly"&amp;gt;
    ...
    &amp;lt;NotFound&amp;gt;
        &amp;lt;PageTitle&amp;gt;Not found&amp;lt;/PageTitle&amp;gt;
        &amp;lt;LayoutView Layout="@typeof(MainLayout)"&amp;gt;
            @* ⬇️⬇️⬇️ This is the content ⬇️⬇️⬇️*@
            &amp;lt;p role="alert"&amp;gt;Sorry, there's nothing at this address.&amp;lt;/p&amp;gt;
            @* ⬆️⬆️⬆️ This is the content ⬆️⬆️⬆️*@
        &amp;lt;/LayoutView&amp;gt;
    &amp;lt;/NotFound&amp;gt;
&amp;lt;/Router&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, all you need to customise your 404 page is this part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor WASM – Custom 404 Page
&lt;/h2&gt;

&lt;p&gt;The sample app used in this post uses a customised 404 page. For the custom 404 page, create a web component called &lt;code&gt;NotFound.razor&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* Fitability.Home.Components.NotFound.razor *@

&amp;lt;div class="row"&amp;gt;
    &amp;lt;div class="col-6"&amp;gt;
        &amp;lt;img src="images/banner-3840x1920.png" class="img-fluid" alt="banner" /&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class="col-6 d-flex align-items-center"&amp;gt;
        &amp;lt;div class="row"&amp;gt;
            &amp;lt;div class="col-12"&amp;gt;
                &amp;lt;h1 class="text-center"&amp;gt;Not Found!&amp;lt;/h1&amp;gt;
                &amp;lt;p class="text-center fs-2"&amp;gt;Your requested page doesn't exist.&amp;lt;/p&amp;gt;
                &amp;lt;p class="text-center fs-3"&amp;gt;&amp;lt;a href="/" class="btn btn-primary btn-lg"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, import it within the &lt;code&gt;LayoutView&lt;/code&gt; component of &lt;code&gt;App.razor&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* Fitability.Home.App.razor *@

&amp;lt;Router AppAssembly="@typeof(App).Assembly"&amp;gt;
    ...
    &amp;lt;NotFound&amp;gt;
        &amp;lt;LayoutView Layout="@typeof(MainLayout)"&amp;gt;
            @* ⬇️⬇️⬇️ Add the NotFound component here ⬇️⬇️⬇️*@
            &amp;lt;NotFound /&amp;gt;
            @* ⬆️⬆️⬆️ Add the NotFound component here ⬆️⬆️⬆️*@
        &amp;lt;/LayoutView&amp;gt;
    &amp;lt;/NotFound&amp;gt;
&amp;lt;/Router&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run your Blazor WASM app on your local machine, and you will see the custom 404 page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdevkimchi.com%2F2022%2F10%2F404-page-of-blazor-wasm-on-gh-pages-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdevkimchi.com%2F2022%2F10%2F404-page-of-blazor-wasm-on-gh-pages-02.png" alt="Custom 404 page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Pages – Default 404 Page
&lt;/h2&gt;

&lt;p&gt;However, deploy your Blazor WASM app to GitHub Pages and visit any non-existing URL. Then you will see GitHub's default 404 page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdevkimchi.com%2F2022%2F10%2F404-page-of-blazor-wasm-on-gh-pages-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdevkimchi.com%2F2022%2F10%2F404-page-of-blazor-wasm-on-gh-pages-03.png" alt="GitHub default 404 page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's because GitHub displays their default 404 page if someone visits the non-existing page. Let's change this.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Pgaes – Custom 404 Page
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;404.html&lt;/code&gt; file MUST physically exist to use the custom 404 page on your GitHub Pages. Generally speaking, you can prepare hard-coded &lt;code&gt;404.html&lt;/code&gt; for this purpose. But we're using the Blazor WASM's routing pipeline.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the existing &lt;code&gt;index.html&lt;/code&gt; file to &lt;code&gt;404.html&lt;/code&gt;. Both are fundamentally the same file as each other.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the &lt;code&gt;404.razor&lt;/code&gt; page and import the &lt;code&gt;NotFound&lt;/code&gt; component. The routing path of this &lt;code&gt;404.razor&lt;/code&gt; page MUST be &lt;code&gt;/404.html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* Fitability.Home.Pages.404.razor *@

@page "/404.html"

&amp;lt;NotFound /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Deploy your Blazor WASM app to GitHub Pages again and visit any non-existing URL. Then, you will be able to see your custom 404 page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdevkimchi.com%2F2022%2F10%2F404-page-of-blazor-wasm-on-gh-pages-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdevkimchi.com%2F2022%2F10%2F404-page-of-blazor-wasm-on-gh-pages-04.png" alt="Custom 404 page on GH Pages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's because GitHub Pages looks for the physical &lt;code&gt;404.html&lt;/code&gt; file. This file runs on the Blazor WASM routing pipeline. Therefore, you can write any business logic on your 404 page.&lt;/p&gt;




&lt;p&gt;So far, we've walked through how to deal with the custom 404 page of the &lt;a href="https://learn.microsoft.com/aspnet/core/blazor/?WT.mc_id=dotnet-77749-juyoo#blazor-webassembly" rel="noopener noreferrer"&gt;Blazor WASM&lt;/a&gt; app running on &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to know more about Blazor?
&lt;/h2&gt;

&lt;p&gt;It's great if you visit those sites for more Blazor stuff.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/learn/aspnet/blazor-tutorial/intro?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Blazor Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/training/paths/build-web-apps-with-blazor/?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Blazor Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>blazorwasm</category>
      <category>githubpages</category>
      <category>404notfound</category>
    </item>
    <item>
      <title>Azure AD B2C on Blazor WASM Standalone</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Sat, 24 Sep 2022 00:00:27 +0000</pubDate>
      <link>https://forem.com/azure/azure-ad-b2c-on-blazor-wasm-standalone-3h2d</link>
      <guid>https://forem.com/azure/azure-ad-b2c-on-blazor-wasm-standalone-3h2d</guid>
      <description>&lt;p&gt;There are many ways to implement authentication and authorisation onto the &lt;a href="https://learn.microsoft.com/aspnet/core/blazor/?WT.mc_id=dotnet-77749-juyoo#blazor-webassembly" rel="noopener noreferrer"&gt;Blazor WebAssembly (WASM)&lt;/a&gt; app. If you publish it to &lt;a href="https://learn.microsoft.com/azure/static-web-apps/overview?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Azure Static Web Apps (ASWA)&lt;/a&gt;, you can use the built-in authN feature (in my &lt;a href="https://dev.to/azure/ms-graph-blazor-webassembly-and-azure-static-web-apps-3p1d"&gt;previous post&lt;/a&gt;). What if you're publishing your Blazor WASM app onto GitHub Pages? There's no built-in authN feature in this case. However, &lt;a href="https://learn.microsoft.com/azure/active-directory-b2c/overview?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Azure AD B2C&lt;/a&gt; offers a straightforward way for it. You can see literally nothing to write codes to make this happen except a few configurations. Throughout this post, I'm going to discuss how to integrate Azure AD B2C with a standalone type Blazor WASM app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure AD vs Azure AD B2C
&lt;/h2&gt;

&lt;p&gt;As the naming implies, both &lt;a href="https://learn.microsoft.com/azure/active-directory/fundamentals/active-directory-whatis?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Azure AD&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/azure/active-directory-b2c/overview?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Azure AD B2C&lt;/a&gt; are similar to each other. In terms of the authentication and authorisation service provider, both are basically the same as each other.&lt;/p&gt;

&lt;p&gt;What are some differences, then? Azure AD does granular controls for internal (organisation-wide) resources like SharePoint, Teams, Dynamics365, etc. Azure AD sets permissions on each resource and account. On the other hand, Azure AD B2C provides the same authN-ing feature, but it's for specific applications. As Azure AD B2C works independently from Azure AD, it's NOT for organisation-wide purposes unless it's configured in that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Azure AD B2C
&lt;/h2&gt;

&lt;p&gt;Create an Azure AD B2C instance from Azure Portal. You can give any name for it, but let's say &lt;code&gt;fitabilitydevkr&lt;/code&gt; for now. Then, you'll get the Azure AD B2C instance like below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-01-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-01-en.png" alt="Azure AD B2C"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "Open B2C Tenant" link to access the Azure AD B2C admin page. On the admin page, click the "App registrations" menu on the left. Then click the "New registration" button at the top.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-02-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-02-en.png" alt="Azure AD B2C - New app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter the details like below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: Name of the app. Say, &lt;code&gt;my-fitability-app&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Supported account types: Choose the option "Accounts in any identity provider or organisational directory".&lt;/li&gt;
&lt;li&gt;Redirect URI (Recommended): Select "Single-page Application (SPA)", then enter &lt;code&gt;https://localhost/authentication/login-callback&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Permissions: Tick "Grant admin consent to openid and offline_access permissions"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-03-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-03-en.png" alt="Azure AD B2C - App registration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once registered, double-check the following details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-page Application – Redirect URIs&lt;/li&gt;
&lt;li&gt;Implicit grant and hybrid flows – Access tokens, ID tokens&lt;/li&gt;
&lt;li&gt;Supported account types – Accounts in any identity provider or organisational directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-04-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-04-en.png" alt="Azure AD B2C - Authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now see the new app registered. Note the application (client) ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-05-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-05-en.png" alt="Azure AD B2C - Client ID"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's define the log-in process. On the Azure AD B2C page, click the "User flows" menu on the left, then the "New user flow" menu at the top.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-06-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-06-en.png" alt="Azure AD B2C - User flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many different user flow types. This time, choose the "Sign up and sign in" flow. Then, select "Recommended".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-07-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-07-en.png" alt="Azure AD B2C - Sign up and sign in #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter the following values, and click "Create".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;SignUpSignIn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Identity providers: Email Signup&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;You can add as many identity providers as you like. But in this post, you can only choose the email sign-up option by default.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-08-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-08-en.png" alt="Azure AD B2C - Sign up and sign in #2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tick more options if you want to include more details in the token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-09-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-09-en.png" alt="Azure AD B2C - Sign up and sign in #3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You now complete Azure AD B2C configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Blazor WASM App
&lt;/h2&gt;

&lt;p&gt;To implement the authN feature with &lt;a href="https://learn.microsoft.com/azure/active-directory-b2c/overview?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Azure AD B2C&lt;/a&gt; on a &lt;a href="https://learn.microsoft.com/aspnet/core/blazor/?WT.mc_id=dotnet-77749-juyoo#blazor-webassembly" rel="noopener noreferrer"&gt;Blazor WASM&lt;/a&gt; app, you first need to create a Blazor WASM project. At the time of this writing, the latest version of &lt;a href="https://visualstudio.microsoft.com/vs/?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt; is &lt;code&gt;17.3.4&lt;/code&gt;, which doesn't create the Blazor WASM project with Azure AD B2C integration, unfortunately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-10-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-10-en.png" alt="Blazor WASM in VS2022"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore, you SHOULD use dotnet CLI for it. Enter the command below. You might be noticed the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--framework net6.0&lt;/code&gt;: Without giving the framework option explicitly, it uses the latest version of the .NET framework. It explicitly declares the &lt;code&gt;net6.0&lt;/code&gt; value to fix the framework version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--hosted false&lt;/code&gt;: If you want to create a standalone Blazor WASM app, use this option. If you want a hosted Blazor WASM app, give this option with &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--auth IndividualB2C&lt;/code&gt;: Choose this option to use Azure AD B2C.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--aad-b2c-instance "https://&amp;lt;TENANT_NAME&amp;gt;.b2clogin.com/"&lt;/code&gt;: It's the login URL of the Azure AD B2C instance. In this post, let's use the tenant name &lt;code&gt;fitabilitydevkr&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--domain "&amp;lt;TENANT_NAME&amp;gt;.onmicrosoft.com"&lt;/code&gt;: It's the domain URL of the Azure AD B2C instance. In this post, let's use the tenant name &lt;code&gt;fitabilitydevkr&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--client-id&lt;/code&gt;: The client ID value from the app created in the previous section.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--susi-policy-id "B2C_1_SignUpSignIn"&lt;/code&gt;: The policy name for authN. Use the policy name that you just created. In this post, use &lt;code&gt;SignUpSignIn&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;blazorwasm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyBlazorWasmApp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--framework&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;net6.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--hosted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IndividualB2C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--aad-b2c-instance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;TENANT_NAME&amp;gt;.b2clogin.com/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;TENANT_NAME&amp;gt;.onmicrosoft.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--client-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;CLIENT_ID&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--susi-policy-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B2C_1_SignUpSignIn"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you've got Azure AD B2C enabled on the Blazor WASM app. Let's configure the app. Open &lt;code&gt;Program.cs&lt;/code&gt; and add the &lt;code&gt;AddMsalAuthentication(...)&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ⬇️⬇️⬇️ Add these lines below ⬇️⬇️⬇️&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMsalAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProviderOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultAccessTokenScopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"openid"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProviderOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultAccessTokenScopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"offline_access"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureAdB2C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProviderOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authentication&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;/// ⬆️⬆️⬆️ Add these lines above ⬆️⬆️⬆️&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, update &lt;code&gt;appsettings.json&lt;/code&gt; under the &lt;code&gt;wwwroot&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AzureAdB2C"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Authority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;TENANT_NAME&amp;gt;.b2clogin.com/&amp;lt;TENANT_NAME&amp;gt;.onmicrosoft.com/B2C_1_SignUpSignIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ClientId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;CLIENT_ID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ValidateAuthority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the configurations on the Blazor WASM app side are done! Build and run the app on your local machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-11-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-11-en.png" alt="Blazor WASM landing page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see the "Log in" button at the top right corner. Click it to see the log-in pop-up window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-12-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-12-en.png" alt="Blazor WASM login page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add your email address and password to log-in, or register a new account by clicking the "Sign up now" button. Once you get logged in, you will see the screen like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-13-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F09%2Fauthn-ing-blazor-wasm-with-azure-ad-b2c-13-en.png" alt="Blazor WASM logged in"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you see your username?&lt;/p&gt;




&lt;p&gt;So far, I've walked through how to add the Azure AD B2C authN feature to the standalone type Blazor WASM app. With minimum code change, you can quickly implement the authN feature. In the next post, I'll give a try to add a social media log-in feature to Azure AD B2C.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to know more about Blazor?
&lt;/h2&gt;

&lt;p&gt;It's great if you visit those sites for more Blazor stuff.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/learn/aspnet/blazor-tutorial/intro?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Blazor Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/training/paths/build-web-apps-with-blazor/?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Blazor Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Want to know more about Azure AD B2C with Blazor?
&lt;/h2&gt;

&lt;p&gt;There are several documents that you might follow for Blazor WASM and Azure AD B2C integration scenarios.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/aspnet/core/blazor/security/webassembly/standalone-with-azure-active-directory-b2c?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Standalone type Blazor WASM with Azure AD B2C&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/aspnet/core/blazor/security/webassembly/hosted-with-azure-active-directory-b2c?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Hosted Blazor WASM with Azure AD B2C&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/aspnet/core/blazor/security/webassembly/additional-scenarios?WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;More security scenarios for Blazor WASM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-user-flow&amp;amp;WT.mc_id=dotnet-77749-juyoo" rel="noopener noreferrer"&gt;Azure AD B2C user flow policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>blazorwasm</category>
      <category>azureadb2c</category>
      <category>authn</category>
    </item>
    <item>
      <title>Browser Extension with Blazor WASM - Cross-Browser Compatibility</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Mon, 05 Sep 2022 12:00:22 +0000</pubDate>
      <link>https://forem.com/dotnet/browser-extension-with-blazor-wasm-cross-browser-compatibility-113l</link>
      <guid>https://forem.com/dotnet/browser-extension-with-blazor-wasm-cross-browser-compatibility-113l</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/dotnet/chrome-extension-with-blazor-wasm-the-integration-5gi2"&gt;previous post&lt;/a&gt;, I discussed the &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;JavaScript interop&lt;/a&gt; feature in &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor WebAssembly (WASM)&lt;/a&gt; and how to leverage it for the &lt;a href="https://developer.chrome.com/docs/extensions/" rel="noopener noreferrer"&gt;Chrome extension&lt;/a&gt; development. In this post, I'm going to expand the same extension to support cross-browser compatibility, like Mozilla Firefox, and what to consider for compatibility.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can download the sample Chrome extension from this GitHub repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/devkimchi" rel="noopener noreferrer"&gt;
        devkimchi
      &lt;/a&gt; / &lt;a href="https://github.com/devkimchi/blazor-wasm-chrome-extension" rel="noopener noreferrer"&gt;
        blazor-wasm-chrome-extension
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This provides sample codes for a Chrome Extension app built on Blazor WASM
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Blazor WASM Browser Extension Sample&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This provides sample codes for a cross-browser extension app built on Blazor WASM. This sample app originally started for building a Chrome extension with Blazor WASM, but it now does the cross-browser support including Chromium-based browsers and Mozilla FireFox.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Acknowledgement&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This sample code includes &lt;a href="https://mozilla.org/" rel="nofollow noopener noreferrer"&gt;Mozilla&lt;/a&gt;'s &lt;a href="https://github.com/mozilla/webextension-polyfill" rel="noopener noreferrer"&gt;WebExtension &lt;code&gt;browser&lt;/code&gt; API Polyfill&lt;/a&gt;, which is licensed under &lt;a href="https://github.com/mozilla/webextension-polyfill/blob/master/LICENSE" rel="noopener noreferrer"&gt;MPL 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Build the app&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;dotnet build &lt;span class="pl-c1"&gt;.&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Publish the app&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;dotnet publish ./src/ChromeExtensionV2/ -c Release -o published&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run PowerShell script&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;.&lt;span class="pl-k"&gt;/&lt;/span&gt;Run&lt;span class="pl-k"&gt;-&lt;/span&gt;PostBuild.ps1&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Register the extension to your Chromium-based browser like &lt;a href="https://www.google.com/chrome/" rel="nofollow noopener noreferrer"&gt;Chrome&lt;/a&gt; or &lt;a href="https://www.microsoft.com/edge?WT.mc_id=dotnet-70466-juyoo" rel="nofollow noopener noreferrer"&gt;Edge&lt;/a&gt;, or &lt;a href="https://www.mozilla.org/firefox/" rel="nofollow noopener noreferrer"&gt;Mozilla FireFox&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visit any website on &lt;a href="https://developer.chrome.com/" rel="nofollow noopener noreferrer"&gt;https://developer.chrome.com&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/" rel="nofollow noopener noreferrer"&gt;https://developer.mozilla.org&lt;/a&gt; or &lt;a href="https://docs.microsoft.com/?WT.mc_id=dotnet-70466-juyoo" rel="nofollow noopener noreferrer"&gt;https://docs.microsoft.com&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the extension by clicking the icon at the top of your web browser.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/devkimchi/blazor-wasm-chrome-extension" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Browser Extension Polyfill
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.w3.org/community/browserext/" rel="noopener noreferrer"&gt;Browser Extension Working Group&lt;/a&gt; at &lt;a href="https://www.w3.org/" rel="noopener noreferrer"&gt;W3.org&lt;/a&gt; proposes the web standards based on the Chrome extension manifest, which supports all web browsers. Based on that proposal, Mozilla has released the &lt;a href="https://github.com/mozilla/webextension-polyfill" rel="noopener noreferrer"&gt;Browser Extension Polyfill&lt;/a&gt; library that supports the modern &lt;a href="https://developer.mozilla.org/docs/Web/JavaScript/Guide/Using_promises" rel="noopener noreferrer"&gt;promise&lt;/a&gt; pattern instead of callback. Therefore, if you import this polyfill library, theoretically, your Chrome extension quickly turns into the browser extension that runs on multiple browser engines.&lt;/p&gt;

&lt;p&gt;Therefore, add the polyfill library through CDN to &lt;code&gt;index.html&lt;/code&gt; like below. The link takes the latest release of the polyfill.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="c"&gt;&amp;lt;!-- ⬇️⬇️⬇️ Add this line ⬇️⬇️⬇️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/browse/webextension-polyfill/dist/browser-polyfill.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬆️⬆️⬆️ Add this line ⬆️⬆️⬆️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.webassembly.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, you can't use the direct link to the polyfill because of the "Content Security Policy" violation error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F08%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-3-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F08%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-3-01.png" alt="Refused to load script"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to sort out this issue, you should download the JavaScript file and give its local reference. Visit the &lt;a href="https://unpkg.com/browse/webextension-polyfill/dist/" rel="noopener noreferrer"&gt;CDN&lt;/a&gt; page and download each files and save them into the &lt;code&gt;wwwroot/js/dist&lt;/code&gt; directory. Then, update the &lt;code&gt;index.html&lt;/code&gt; file like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="c"&gt;&amp;lt;!-- ⬇️⬇️⬇️ Add this line ⬇️⬇️⬇️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"js/dist/browser-polyfill.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬆️⬆️⬆️ Add this line ⬆️⬆️⬆️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.webassembly.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, it's OK to use the polyfill. Next, let's move on to &lt;code&gt;manifest.json&lt;/code&gt; for cross-browser support.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;manifest.json&lt;/code&gt; Accommodation
&lt;/h2&gt;

&lt;p&gt;First of all, you need to get rid of Chrome's exclusive features. Especially, as the &lt;a href="https://developer.chrome.com/docs/extensions/reference/declarativeContent/" rel="noopener noreferrer"&gt;Declarative Content&lt;/a&gt; part only works for the Chrome extensions, you need to replace it with other approaches. In this example, if you want the extension to only work with the domain like &lt;em&gt;developer.chrome.com&lt;/em&gt;, &lt;em&gt;developer.mozilla.org&lt;/em&gt; or &lt;em&gt;docs.microsoft.com&lt;/em&gt;, add domain URLs to the &lt;code&gt;permissions&lt;/code&gt; collection like below and remove the &lt;code&gt;declarativeContent&lt;/code&gt; value from it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"*://developer.chrome.com/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"*://developer.mozilla.org/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"*://docs.microsoft.com/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"activeTab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"declarativeContent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add the polyfill to the background script collection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"js/dist/browser-polyfill.min.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"js/background.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"persistent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially, &lt;code&gt;manifest.json&lt;/code&gt; only uses the &lt;code&gt;options_page&lt;/code&gt; attribute, but for compatibility, add the &lt;code&gt;options_ui&lt;/code&gt; attribute as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"options_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"options.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"options_ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"options.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"browser_style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, Mozilla-based browsers need a unique ID for each extension. Therefore, give the &lt;code&gt;browser_specific_settings&lt;/code&gt; for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"browser_specific_settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gecko"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browser-extension-sample@devkimchi.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updating &lt;code&gt;manifest.json&lt;/code&gt; is over. Now, let's move on to the JavaScript files.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;background.js&lt;/code&gt; Update
&lt;/h2&gt;

&lt;p&gt;For &lt;code&gt;background.js&lt;/code&gt;, you must change all the &lt;code&gt;chrome.&lt;/code&gt; instances to the &lt;code&gt;browser.&lt;/code&gt; ones because the &lt;code&gt;chrome&lt;/code&gt; instances are specific to the Chrome extension, while the &lt;code&gt;browser&lt;/code&gt; instances are for the browser extensions in general. In other words, if your &lt;code&gt;background.js&lt;/code&gt; looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onInstalled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3aa757&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The color is green.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onPageChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeRules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onPageChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addRules&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
      &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PageStateMatcher&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;pageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hostEquals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;developer.chrome.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PageStateMatcher&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;pageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hostEquals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docs.microsoft.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ShowPageAction&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="p"&gt;}]);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All &lt;code&gt;chrome.&lt;/code&gt; declared above must be replaced with &lt;code&gt;browser.&lt;/code&gt; like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onInstalled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
  &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3aa757&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The color is green.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
  &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onPageChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeRules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
    &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onPageChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addRules&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
      &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PageStateMatcher&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;pageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hostEquals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;developer.browser.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PageStateMatcher&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;pageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hostEquals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docs.microsoft.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
      &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ShowPageAction&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="p"&gt;}]);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you replace all the &lt;code&gt;chrome&lt;/code&gt; instances with &lt;code&gt;browser&lt;/code&gt; ones, change all the callback patterns to promise patterns. For example, this example requires the browser's local storage access. How can it be changed?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Before&lt;/span&gt;
  &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3aa757&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The color is green.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// After&lt;/span&gt;
  &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3aa757&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The color is green.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's written as an anonymous function and added as the event handler to the &lt;code&gt;onInstalled&lt;/code&gt; event. Therefore, it's better to create an independent function like the below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleRuntimeOnInstalled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3aa757&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The color is green.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned above, we can't use the &lt;a href="https://developer.chrome.com/docs/extensions/reference/declarativeContent/" rel="noopener noreferrer"&gt;Declarative Content&lt;/a&gt; feature any longer. Hence, instead of the events that belong to the &lt;code&gt;declarativeContent&lt;/code&gt; property, you need other events and event handlers. This sample extension only works in specific domains like &lt;em&gt;developer.google.com&lt;/em&gt;, &lt;em&gt;developer.mozilla.org&lt;/em&gt; or &lt;em&gt;docs.microsoft.com&lt;/em&gt;, and the &lt;code&gt;declarativeContent&lt;/code&gt; property performs this sort of detection. Therefore, replace it with the &lt;code&gt;handleTabs()&lt;/code&gt; function like below, using the &lt;code&gt;pageAction&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleTabs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;developer.chrome.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;developer.mozilla.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docs.microsoft.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is done, register both event handlers for the events below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onInstalled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleRuntimeOnInstalled&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onActivated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleTabs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onHighlighted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleTabs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onUpdated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleTabs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;popup.js&lt;/code&gt; Update
&lt;/h2&gt;

&lt;p&gt;Let's update &lt;code&gt;popup.js&lt;/code&gt;. It was initially like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;changeColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changeColor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;changeColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;changeColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;changeColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document.body.style.backgroundColor = "&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;";&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like the same in the previous JavaScript file,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replace all &lt;code&gt;chrome.&lt;/code&gt; instances with &lt;code&gt;browser.&lt;/code&gt; ones.&lt;/li&gt;
&lt;li&gt;Change all callback patterns to the promise patterns.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;changeColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changeColor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
&lt;span class="c1"&gt;// Use the promise pattern&lt;/span&gt;
&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;changeColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;changeColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;changeColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
  &lt;span class="c1"&gt;// Use the promise pattern&lt;/span&gt;
  &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;developer.chrome.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;developer.mozilla.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docs.microsoft.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
      &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document.body.style.backgroundColor = "&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;";&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;URL not matched&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;popup.js&lt;/code&gt; is done.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;options.js&lt;/code&gt; Update
&lt;/h2&gt;

&lt;p&gt;This time, it's the turn for &lt;code&gt;options.js&lt;/code&gt;, which initially looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;buttonDiv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kButtonColors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3aa757&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#e8453c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f9bb2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#4688f1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;constructOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kButtonColors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;kButtonColors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color is &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;constructOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kButtonColors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Replace all &lt;code&gt;chrome.&lt;/code&gt; instances with &lt;code&gt;browser.&lt;/code&gt; ones.&lt;/li&gt;
&lt;li&gt;Change all callback patterns to the promise patterns.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;buttonDiv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kButtonColors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3aa757&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#e8453c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f9bb2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#4688f1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;constructOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kButtonColors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;kButtonColors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Use 'browser.' instead of 'chrome.'&lt;/span&gt;
      &lt;span class="c1"&gt;// Use the promise pattern&lt;/span&gt;
      &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color is &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;constructOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kButtonColors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;options.js&lt;/code&gt; file is updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor Component Abstraction
&lt;/h2&gt;

&lt;p&gt;Suppose you want to get rid of importing the browser polyfill from &lt;code&gt;index.html&lt;/code&gt; and import it directly from the Blazor components. In that case, it's a good idea to create a common page component that each &lt;code&gt;Popup.razor&lt;/code&gt; and &lt;code&gt;Options.razor&lt;/code&gt; can inherit. First of all, declare a &lt;code&gt;LoadAdditionalJsAsync()&lt;/code&gt; method that is called within the &lt;code&gt;OnAfterRenderAsync(...)&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PageComponentBase&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ComponentBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;LoadAdditionalJsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, invoke the method at the end of &lt;code&gt;OnAfterRenderAsync(...)&lt;/code&gt;. By doing so, the &lt;code&gt;OnAfterRenderAsync(...)&lt;/code&gt; method first imports the &lt;code&gt;js/main.js&lt;/code&gt; followed by the browser polyfill script, then import the page-specific JavaScript files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PageComponentBase&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ComponentBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IJSRuntime&lt;/span&gt; &lt;span class="n"&gt;JS&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="n"&gt;IJSObjectReference&lt;/span&gt; &lt;span class="n"&gt;Module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnAfterRenderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IJSObjectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./js/main.js"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"js/dist/browser-polyfill.min.js"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"loadJs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Invoke the page-specific JavaScript loader&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadAdditionalJsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;LoadAdditionalJsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;Popup.razor&lt;/code&gt; page, inherit the &lt;code&gt;PageComponentBase&lt;/code&gt; class and implement the &lt;code&gt;LoadAdditionalJsAsync()&lt;/code&gt; method like below, which imports the &lt;code&gt;js/popup.js&lt;/code&gt; file. You don't need the &lt;code&gt;IJSRuntime&lt;/code&gt; instance as a dependency any longer, so remove it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/popup.html"&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;@inject&lt;/span&gt; &lt;span class="n"&gt;IJSRuntime&lt;/span&gt; &lt;span class="n"&gt;JS&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;

&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;ChromeExtensionV2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Components&lt;/span&gt;
&lt;span class="n"&gt;@inherits&lt;/span&gt; &lt;span class="n"&gt;PageComponentBase&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;LoadAdditionalJsAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"js/popup.js"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"loadJs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, the &lt;code&gt;Options.razor&lt;/code&gt; page also inherits the &lt;code&gt;PageComponentBase&lt;/code&gt; class and implement the &lt;code&gt;LoadAdditionalJsAsync()&lt;/code&gt; method to import &lt;code&gt;js/options.js&lt;/code&gt;. The &lt;code&gt;IJSRuntime&lt;/code&gt; dependency is no longer necessary, either.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/options.html"&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;@inject&lt;/span&gt; &lt;span class="n"&gt;IJSRuntime&lt;/span&gt; &lt;span class="n"&gt;JS&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;

&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;ChromeExtensionV2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Components&lt;/span&gt;
&lt;span class="n"&gt;@inherits&lt;/span&gt; &lt;span class="n"&gt;PageComponentBase&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;LoadAdditionalJsAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"js/options.js"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"loadJs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;Run-PostBuild.ps1&lt;/code&gt; Adjustment
&lt;/h2&gt;

&lt;p&gt;Unlike the Chrome extensions, the Mozilla-based web extension needs a &lt;code&gt;.zip&lt;/code&gt; file for installation. Therefore, the PowerShell script, &lt;code&gt;Run-PostBuild.ps1&lt;/code&gt;, requires an additional step to generate a &lt;code&gt;.zip&lt;/code&gt; file as an artifact.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Compress-Archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/published/wwwroot/&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DestinationPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/published/wwwroot/wwwroot.zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you complete all the steps above, build the project and run the PowerShell script, and it will generate the &lt;code&gt;wwwroot.zip&lt;/code&gt; file under the &lt;code&gt;published&lt;/code&gt; directory. Next, install the zip file to Firefox, and you will see the screen below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F08%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-3-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F08%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-3-02.png" alt="Browser extension on FireFox #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the options page as a pop-up modal due to the &lt;code&gt;manifest.json&lt;/code&gt; update. In the options modal, change the background colour to yellow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F08%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-3-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F08%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-3-03.png" alt="Browser extension options on FireFox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, you are able to change the background colour to yellow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F08%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-3-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F08%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-3-04.png" alt="Browser extension on FireFox #2"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;So far, we've walked through how our Blazor WASM-based chrome extension can support cross-browser compatibility and showed how it works on the Firefox browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you want to know more about Blazor?
&lt;/h2&gt;

&lt;p&gt;Here are some tutorials for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/learn/aspnet/blazor-tutorial/intro?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/learn/paths/build-web-apps-with-blazor/?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>blazorwasm</category>
      <category>browserextension</category>
      <category>jsinterop</category>
    </item>
    <item>
      <title>Chrome Extension with Blazor WASM - The Integration</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Fri, 22 Jul 2022 00:00:31 +0000</pubDate>
      <link>https://forem.com/dotnet/chrome-extension-with-blazor-wasm-the-integration-5gi2</link>
      <guid>https://forem.com/dotnet/chrome-extension-with-blazor-wasm-the-integration-5gi2</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/dotnet/chrome-extension-with-blazor-wasm-the-migration-eb7"&gt;previous post&lt;/a&gt;, I've walked through how to migrate a JavaScript-based &lt;a href="https://developer.chrome.com/docs/extensions/" rel="noopener noreferrer"&gt;Chrome extension&lt;/a&gt; to &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor WASM&lt;/a&gt; with minimal code changes. Although it's successfully migrated to Blazor WASM, it doesn't fully use the &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;JavaScript Interoperability (JS interop)&lt;/a&gt; feature, which is the powerful feature of Blazor WASM. I'm going to take this feature throughout this post.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can download the sample Chrome extension from this GitHub repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/devkimchi" rel="noopener noreferrer"&gt;
        devkimchi
      &lt;/a&gt; / &lt;a href="https://github.com/devkimchi/blazor-wasm-chrome-extension" rel="noopener noreferrer"&gt;
        blazor-wasm-chrome-extension
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This provides sample codes for a Chrome Extension app built on Blazor WASM
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Blazor WASM Browser Extension Sample&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This provides sample codes for a cross-browser extension app built on Blazor WASM. This sample app originally started for building a Chrome extension with Blazor WASM, but it now does the cross-browser support including Chromium-based browsers and Mozilla FireFox.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Acknowledgement&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This sample code includes &lt;a href="https://mozilla.org/" rel="nofollow noopener noreferrer"&gt;Mozilla&lt;/a&gt;'s &lt;a href="https://github.com/mozilla/webextension-polyfill" rel="noopener noreferrer"&gt;WebExtension &lt;code&gt;browser&lt;/code&gt; API Polyfill&lt;/a&gt;, which is licensed under &lt;a href="https://github.com/mozilla/webextension-polyfill/blob/master/LICENSE" rel="noopener noreferrer"&gt;MPL 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Build the app&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;dotnet build &lt;span class="pl-c1"&gt;.&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Publish the app&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;dotnet publish ./src/ChromeExtensionV2/ -c Release -o published&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run PowerShell script&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;.&lt;span class="pl-k"&gt;/&lt;/span&gt;Run&lt;span class="pl-k"&gt;-&lt;/span&gt;PostBuild.ps1&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Register the extension to your Chromium-based browser like &lt;a href="https://www.google.com/chrome/" rel="nofollow noopener noreferrer"&gt;Chrome&lt;/a&gt; or &lt;a href="https://www.microsoft.com/edge?WT.mc_id=dotnet-70466-juyoo" rel="nofollow noopener noreferrer"&gt;Edge&lt;/a&gt;, or &lt;a href="https://www.mozilla.org/firefox/" rel="nofollow noopener noreferrer"&gt;Mozilla FireFox&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visit any website on &lt;a href="https://developer.chrome.com/" rel="nofollow noopener noreferrer"&gt;https://developer.chrome.com&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/" rel="nofollow noopener noreferrer"&gt;https://developer.mozilla.org&lt;/a&gt; or &lt;a href="https://docs.microsoft.com/?WT.mc_id=dotnet-70466-juyoo" rel="nofollow noopener noreferrer"&gt;https://docs.microsoft.com&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the extension by clicking the icon at the top of your web browser.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/devkimchi/blazor-wasm-chrome-extension" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Chrome Extension – Before JS Interop
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;index.html&lt;/code&gt; file written in the &lt;a href="https://dev.to/dotnet/chrome-extension-with-blazor-wasm-the-migration-eb7"&gt;previous post&lt;/a&gt; looks like the following. It loads &lt;code&gt;blazor.webassembly.js&lt;/code&gt; first with the &lt;code&gt;autostart="false"&lt;/code&gt; option, followed by loading &lt;code&gt;js/main.js&lt;/code&gt; through the function call. The &lt;code&gt;js/main.js&lt;/code&gt; reference is replaced with &lt;code&gt;js/options.js&lt;/code&gt; or &lt;code&gt;js/popup.js&lt;/code&gt; during the artifact generation process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="c"&gt;&amp;lt;!-- Add the 'autostart' attribute and set its value to 'false' --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.webassembly.js"&lt;/span&gt; &lt;span class="na"&gt;autostart=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬇️⬇️⬇️ Add these lines ⬇️⬇️⬇️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Blazor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;customScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;customScript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js/main.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customScript&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬆️⬆️⬆️ Add these lines ⬆️⬆️⬆️ --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm not happy with this JS loading due to the two reasons below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I have to explicitly give the option of &lt;code&gt;autostart="false"&lt;/code&gt; while loading the &lt;code&gt;blazor.webassembly.js&lt;/code&gt; file, which is extra to the bootstrapper.&lt;/li&gt;
&lt;li&gt;I have to append &lt;code&gt;js/main.js&lt;/code&gt; through the Promise pattern after &lt;code&gt;Blazor.start()&lt;/code&gt;, which is another extra point to the bootstrapper.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Can we minimise this modification from the original &lt;code&gt;index.html&lt;/code&gt; file and use more JS Interop capabilities here so that it can be more Blazor-ish?&lt;/p&gt;

&lt;h2&gt;
  
  
  Chrome Extension – JS Interop Step #1
&lt;/h2&gt;

&lt;p&gt;Let's update the &lt;code&gt;index.html&lt;/code&gt; file. Unlike the previous update, remove the JS part calling the &lt;code&gt;Blazor.start()&lt;/code&gt; function. Load &lt;code&gt;js/main.js&lt;/code&gt; before loading &lt;code&gt;blazor.webassembly.js&lt;/code&gt;. Remove the &lt;code&gt;autostart="false"&lt;/code&gt; attribute as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="c"&gt;&amp;lt;!-- ⬇️⬇️⬇️ Add this line ⬇️⬇️⬇️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"js/main.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬆️⬆️⬆️ Add this line ⬆️⬆️⬆️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.webassembly.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Originally the &lt;code&gt;js/main.js&lt;/code&gt; file was blank, but this time let's add the following JS function that appends another &lt;code&gt;script&lt;/code&gt; tag to load the given JS file reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadJs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid source URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sourceUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/javascript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Script loaded successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to load script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;Popup.razor&lt;/code&gt; file like below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the &lt;code&gt;@inject&lt;/code&gt; declaration for the &lt;code&gt;IJSRuntime&lt;/code&gt; instance as a dependency.&lt;/li&gt;
&lt;li&gt;Call the &lt;code&gt;JS.InvokeVoidAsync&lt;/code&gt; method to load the &lt;code&gt;js/popup.js&lt;/code&gt; file by invoking the &lt;code&gt;loadJs&lt;/code&gt; function from the &lt;code&gt;js/main.js&lt;/code&gt; file.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* Popup.razor *@

@page "/popup.html"

@* Inject IJSRuntime instance *@
@inject IJSRuntime JS

...

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
        {
            return;
        }

        var src = "js/popup.js";

        // Invoke the `loadJs` function
        await JS.InvokeVoidAsync("loadJs", src).ConfigureAwait(false);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;Options.razor&lt;/code&gt; file in the same way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* Options.razor *@

@page "/options.html"

@* Inject IJSRuntime instance *@
@inject IJSRuntime JS

...

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
        {
            return;
        }

        var src = "js/options.js";

        // Invoke the `loadJs` function
        await JS.InvokeVoidAsync("loadJs", src).ConfigureAwait(false);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we don't need the reference replacement part in the PowerShell script. Let's comment them out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run-PostBuild.ps1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Update-FileContent `&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#     -Filename "./published/wwwroot/popup.html" `&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#     -Value1 "js/main.js" `&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#     -Value2 "js/popup.js"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Update-FileContent `&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#     -Filename "./published/wwwroot/options.html" `&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#     -Value1 "js/main.js" `&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#     -Value2 "js/options.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build and publish the Blazor WASM app, then run the PowerShell script to get ready for the extension loading. Once reload the extension, it works with no issue. The &lt;code&gt;loadJs&lt;/code&gt; function is the key that takes advantage of the JS Interop feature.&lt;/p&gt;

&lt;p&gt;However, I'm still not happy with adding &lt;code&gt;js/main.js&lt;/code&gt; to &lt;code&gt;index.html&lt;/code&gt;. Can we also remove this part from the file and use the JS Interop feature instead?&lt;/p&gt;

&lt;h2&gt;
  
  
  Chrome Extension – JS Interop Step #2
&lt;/h2&gt;

&lt;p&gt;Let's get &lt;code&gt;index.html&lt;/code&gt; back to the original state when a bootstrapper creates the file. Then, all we can see in &lt;code&gt;index.html&lt;/code&gt; is the &lt;code&gt;blazor.webassembly.js&lt;/code&gt; file reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;ChromeExtensionV2&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;base&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"css/bootstrap/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"css/app.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"ChromeExtensionV2.styles.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"blazor-error-ui"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        An unhandled error has occurred.
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"reload"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Reload&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dismiss"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;🗙&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.webassembly.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add the &lt;code&gt;export&lt;/code&gt; declaration in front of the &lt;code&gt;loadJs&lt;/code&gt; function in the &lt;code&gt;js/main.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadJs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update &lt;code&gt;Popup.razor&lt;/code&gt; like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"js/popup.js"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Import the `js/main.js` file&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;JS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IJSObjectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./js/main.js"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Invoke the `loadJs` function&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"loadJs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same change should also be applicable to &lt;code&gt;Options.razor&lt;/code&gt;. And finally, update the &lt;code&gt;manifest.json&lt;/code&gt; below because we no longer need the hash key for &lt;code&gt;popup.js&lt;/code&gt; and &lt;code&gt;options.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Getting Started Example (Blazor WASM)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Build an Extension!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"content_security_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval' 'sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA='; object-src 'self'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build and publish the app, and run the PowerShell script against the artifact. Then, reload the extension, and you will see the same result.&lt;/p&gt;




&lt;p&gt;So far, we've walked through how to take more advantage of the &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;JS Interop&lt;/a&gt; feature that &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor WASM&lt;/a&gt; offers to migrate the existing Chrome extension to Blazor WASM. What could be the potential merits of this exercise?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We never touch any bootstrapper codes that Blazor WASM generate for us.&lt;/li&gt;
&lt;li&gt;If necessary, we load JavaScript for each page using the JS Interop feature. During this practice, C# handles all the JS codes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then, does this exercise only brings you benefits? Here are a couple of considerations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The code gets overly complex. If we simply import the JavaScript files through the &lt;code&gt;index.html&lt;/code&gt;/&lt;code&gt;popup.html&lt;/code&gt;/&lt;code&gt;options.html&lt;/code&gt;, we don't need to do this exercise.&lt;/li&gt;
&lt;li&gt;Not everytime the dynamic JS loading is useful. It has trade-offs. If you don't want to touch the bootstrapper files, then try this approach discussed in this post. But if you do touch the bootstrapper files, then this dynamic JS loading approach may be unsuitable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overall, if we use more JS Interop features appropriately, we can build the Blazor WASM app more effectively, which will be another option for building Chrome extensions. In the &lt;a href="https://dev.to/dotnet/browser-extension-with-blazor-wasm-cross-browser-compatibility-113l"&gt;next post&lt;/a&gt;, I'm going to discuss cross-browser compatibility for this Blazor WASM-based browser extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you want to know more about Blazor?
&lt;/h2&gt;

&lt;p&gt;Here are some tutorials for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/learn/aspnet/blazor-tutorial/intro?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/learn/paths/build-web-apps-with-blazor/?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>blazorwasm</category>
      <category>browserextension</category>
      <category>jsinterop</category>
    </item>
    <item>
      <title>Chrome Extension with Blazor WASM - The Migration</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Fri, 08 Jul 2022 00:00:30 +0000</pubDate>
      <link>https://forem.com/dotnet/chrome-extension-with-blazor-wasm-the-migration-eb7</link>
      <guid>https://forem.com/dotnet/chrome-extension-with-blazor-wasm-the-migration-eb7</guid>
      <description>&lt;p&gt;A &lt;a href="https://developer.chrome.com/docs/extensions/" rel="noopener noreferrer"&gt;Chrome extension&lt;/a&gt; is a tiny app used for Chromium-based web browsers for many purposes. It's like a web application serving static contents, which doesn't need a web server. If this is the case, what if we can build the Chrome extension using &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor WebAssembly&lt;/a&gt;? Throughout this post, I'm going to migrate the existing JavaScript-based Chrome extension to Blazor WebAssembly with minimal code changes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can download the sample Chrome extension from this GitHub repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/devkimchi" rel="noopener noreferrer"&gt;
        devkimchi
      &lt;/a&gt; / &lt;a href="https://github.com/devkimchi/blazor-wasm-chrome-extension" rel="noopener noreferrer"&gt;
        blazor-wasm-chrome-extension
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This provides sample codes for a Chrome Extension app built on Blazor WASM
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Blazor WASM Browser Extension Sample&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This provides sample codes for a cross-browser extension app built on Blazor WASM. This sample app originally started for building a Chrome extension with Blazor WASM, but it now does the cross-browser support including Chromium-based browsers and Mozilla FireFox.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Acknowledgement&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This sample code includes &lt;a href="https://mozilla.org/" rel="nofollow noopener noreferrer"&gt;Mozilla&lt;/a&gt;'s &lt;a href="https://github.com/mozilla/webextension-polyfill" rel="noopener noreferrer"&gt;WebExtension &lt;code&gt;browser&lt;/code&gt; API Polyfill&lt;/a&gt;, which is licensed under &lt;a href="https://github.com/mozilla/webextension-polyfill/blob/master/LICENSE" rel="noopener noreferrer"&gt;MPL 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Build the app&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;dotnet build &lt;span class="pl-c1"&gt;.&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Publish the app&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;dotnet publish ./src/ChromeExtensionV2/ -c Release -o published&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run PowerShell script&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;.&lt;span class="pl-k"&gt;/&lt;/span&gt;Run&lt;span class="pl-k"&gt;-&lt;/span&gt;PostBuild.ps1&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Register the extension to your Chromium-based browser like &lt;a href="https://www.google.com/chrome/" rel="nofollow noopener noreferrer"&gt;Chrome&lt;/a&gt; or &lt;a href="https://www.microsoft.com/edge?WT.mc_id=dotnet-70466-juyoo" rel="nofollow noopener noreferrer"&gt;Edge&lt;/a&gt;, or &lt;a href="https://www.mozilla.org/firefox/" rel="nofollow noopener noreferrer"&gt;Mozilla FireFox&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visit any website on &lt;a href="https://developer.chrome.com/" rel="nofollow noopener noreferrer"&gt;https://developer.chrome.com&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/" rel="nofollow noopener noreferrer"&gt;https://developer.mozilla.org&lt;/a&gt; or &lt;a href="https://docs.microsoft.com/?WT.mc_id=dotnet-70466-juyoo" rel="nofollow noopener noreferrer"&gt;https://docs.microsoft.com&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the extension by clicking the icon at the top of your web browser.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/devkimchi/blazor-wasm-chrome-extension" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Chrome Extension – JavaScript-based
&lt;/h2&gt;

&lt;p&gt;There's a &lt;a href="https://github.com/devkimchi/blazor-wasm-chrome-extension/tree/the-migration/src/chrome-extension-v2" rel="noopener noreferrer"&gt;Chrome extension app&lt;/a&gt; based on the &lt;a href="https://developer.chrome.com/docs/extensions/mv2/getstarted/" rel="noopener noreferrer"&gt;Manifest v2&lt;/a&gt;. Load it to your &lt;a href="https://www.microsoft.com/edge?WC.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Edge browser&lt;/a&gt;, and it will look like the following image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-01.png" alt="Chrome Extension on Microsoft Edge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can change the background colour if you run it on your web browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-02-ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-02-ko.png" alt="Chrome Extension to change background colour #1"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-03-ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-03-ko.png" alt="Chrome Extension that has changed the background colour #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because it's written in JavaScript, let's migrate this app to Blazor WebAssembly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Due to the policy change, you cannot publish any Chrome extension based on the manifest v2 to Web Store from January 17th, 2022. I'm going to discuss the manifest v3 extension in the following posts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Chrome Extension – Blazor WebAssembly-based
&lt;/h2&gt;

&lt;p&gt;Create a Blazor WebAssembly project. In the newly created project, open the &lt;code&gt;index.html&lt;/code&gt; file, and you will see the &lt;code&gt;div&lt;/code&gt; tag having the &lt;code&gt;id&lt;/code&gt; attribute of &lt;code&gt;app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-04.png" alt="Visual Studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basically, Blazor WebAssembly handles the &lt;a href="https://en.wikipedia.org/wiki/Virtual_DOM" rel="noopener noreferrer"&gt;virtual DOM&lt;/a&gt; within the &lt;code&gt;div&lt;/code&gt; tag with the &lt;code&gt;app&lt;/code&gt; ID. Therefore, your Chrome extension should accommodate it. In the coming posts, I'll talk about how you manage the virtual DOM in a Blazor WebAssembply app.&lt;/p&gt;

&lt;p&gt;There are several differences between the Chrome extension and the Blazor WebAssembly app. One of the differences is routing. Because Blazor WebAssembly relies on the routing with the virtual DOM, you only need one physical file, &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Delete both &lt;code&gt;Counter.razor&lt;/code&gt; and &lt;code&gt;FetchData.razor&lt;/code&gt;, and add &lt;code&gt;Popup.razor&lt;/code&gt; page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* Popup.razor *@

@page "/popup.html"

&amp;lt;PageTitle&amp;gt;Pop Up&amp;lt;/PageTitle&amp;gt;

&amp;lt;h1&amp;gt;Pop Up - Chrome Extension with Blazor WASM&amp;lt;/h1&amp;gt;

&amp;lt;button id="changeColor"&amp;gt;&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add &lt;code&gt;Options.razor&lt;/code&gt; page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* Options.razor *@

@page "/options.html"

&amp;lt;PageTitle&amp;gt;Options&amp;lt;/PageTitle&amp;gt;

&amp;lt;h1&amp;gt;Options - Chrome Extension with Blazor WASM&amp;lt;/h1&amp;gt;

&amp;lt;div id="buttonDiv"&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;Choose a different background color!&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, each Razor page has the routing value of &lt;code&gt;popup.html&lt;/code&gt; and &lt;code&gt;options.html&lt;/code&gt;, respectively.&lt;/p&gt;

&lt;p&gt;Once all is done, publish your Blazor WebAssembly app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/src/ChromeExtensionV2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can confirm the published artifact:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-05.png" alt="Chrome Extension published #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's time to register this migrated extension. However, you can't register it because of the error message below. The error message says that you can't use any directory name or filename starting with an underscore (&lt;code&gt;_&lt;/code&gt;). Unfortunately, the Blazor WebAssembly app generates the &lt;code&gt;_framework&lt;/code&gt; directory by default. Therefore, you can't directly use the published artifact.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-06.png" alt="Chrome Extension registration error #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It would be best if you changed all the underscored directories and filenames. In addition to that, it would be best if you change all the contents referencing underscored files or directories. You can easily do this with a PowerShell script or a bash shell script. I'm going to use the PowerShell script for this post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run-PostBuild.ps1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Update-FileContent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Value2&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Raw&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Value2&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Update-FileContent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./published/wwwroot/_framework/blazor.webassembly.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Value1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"_framework"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Value2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"framework"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Update-FileContent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./published/wwwroot/index.html"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Value1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"_framework"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Value2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"framework"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;mv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/published/wwwroot/_framework&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/published/wwwroot/framework&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the PowerShell script, you'll be able to see the directory name changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-07.png" alt="Chrome Extension published #2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Register the extension again. This time, another error occurs that no &lt;code&gt;options.html&lt;/code&gt; file exists. In other words, you physically need the &lt;code&gt;options.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-08.png" alt="Chrome Extension registration error #2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to this error, any Chrome extension must have &lt;code&gt;popup.html&lt;/code&gt; and/or &lt;code&gt;options.html&lt;/code&gt; files. Both Razor files are not enough for page rendering. By the way, both &lt;code&gt;popup.html&lt;/code&gt; and &lt;code&gt;options.html&lt;/code&gt; files are fundamentally the same as the &lt;code&gt;index.html&lt;/code&gt; that access the &lt;code&gt;div&lt;/code&gt; tag having the &lt;code&gt;app&lt;/code&gt; ID to handle the virtual DOM. So then, instead of manually creating both files, let's duplicate the existing &lt;code&gt;index.html&lt;/code&gt; file by adding those commands to the PowerShell script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run-PostBuild.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/published/wwwroot/index.html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/published/wwwroot/popup.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/published/wwwroot/index.html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/published/wwwroot/options.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rerun the script. Both &lt;code&gt;popup.html&lt;/code&gt; and &lt;code&gt;options.html&lt;/code&gt; are duplicated from &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-09.png" alt="Chrome Extension published #3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Register the extension again. No error has occurred.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-10.png" alt="Chrome Extension registered #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, no error occurring during the registration process doesn't mean that the extension actually works. Click the "Inspect pop-up window" menu like the screenshot below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-11-ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-11-ko.png" alt="Chrome Extension inspect pop-up window"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, you'll see the developer tools window that says there are errors. It's because of the &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_security_policy" rel="noopener noreferrer"&gt;Content Security Policy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-12.png" alt="Chrome Extension pop-up error #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To sort out this error, the &lt;code&gt;manifest.json&lt;/code&gt; file needs the &lt;code&gt;content_security_policy&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Getting Started Example (Blazor WASM)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Build an Extension!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"content_security_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval' 'sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA='; object-src 'self'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are some details for the &lt;code&gt;content_security_policy&lt;/code&gt; attribute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;unsafe-eval&lt;/code&gt;: For the &lt;code&gt;Function()&lt;/code&gt; object, and &lt;code&gt;setTimeout()&lt;/code&gt; and &lt;code&gt;setInterval()&lt;/code&gt; functions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasm-unsafe-eval&lt;/code&gt;: For the web assembly binary files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA=&lt;/code&gt;: For the Blazor WebAssembly libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After updating the &lt;code&gt;manifest.json&lt;/code&gt; file, register the extension again. Everything looks OK.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-13-ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-13-ko.png" alt="Chrome Extension registered #2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, we still don't see the button to change the background colour. It means that the extension still doesn't work as expected. It's because both &lt;code&gt;popup.html&lt;/code&gt; and &lt;code&gt;options.html&lt;/code&gt; don't have corresponding JavaScript files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-14.png" alt="Chrome Extension popup.html #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both files are automatically generated from &lt;code&gt;index.html&lt;/code&gt; through the PowerShell script. Therefore, add the JavaScript reference of &lt;code&gt;js/main.js&lt;/code&gt; to &lt;code&gt;index.html&lt;/code&gt;. Also, create an empty &lt;code&gt;main.js&lt;/code&gt; file under the &lt;code&gt;js&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.webassembly.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬇️⬇️⬇️ Add this line ⬇️⬇️⬇️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"js/main.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬆️⬆️⬆️ Add this line ⬆️⬆️⬆️ --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, update the PowerShell script by adding the following lines so that both &lt;code&gt;popup.html&lt;/code&gt; and &lt;code&gt;options.html&lt;/code&gt; have the link to &lt;code&gt;popup.js&lt;/code&gt; and &lt;code&gt;options.js&lt;/code&gt;, respectively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run-PostBuild.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Update-FileContent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./published/wwwroot/popup.html"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Value1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"js/main.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Value2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"js/popup.js"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Update-FileContent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./published/wwwroot/options.html"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Value1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"js/main.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Value2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"js/options.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-register the extension and see how it's going. But another error has occurred this time. Both JavaScript files worked perfectly with no issue but not in the Blazor WebAssembly. Why is that?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-15-ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-15-ko.png" alt="Chrome Extension pop-up error #2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;blazor.webassembly.js&lt;/code&gt; file knows the answer. This JavaScript file initiates the Blazor WebAssembly app, followed by loading either &lt;code&gt;popup.js&lt;/code&gt; or &lt;code&gt;options.js&lt;/code&gt;. Unfortunately, those files are loaded even before the Blazor app is initiated, which causes the error above.&lt;/p&gt;

&lt;p&gt;Therefore, to figure out this problem, you need to update the JavaScript reference section like below. &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;JS interop for Blazor WebAssembly&lt;/a&gt; has more information if you want to know more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="c"&gt;&amp;lt;!-- Add the 'autostart' attribute and set its value to 'false' --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.webassembly.js"&lt;/span&gt; &lt;span class="na"&gt;autostart=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬇️⬇️⬇️ Add these lines ⬇️⬇️⬇️ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Blazor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;customScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;customScript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js/main.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customScript&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ⬆️⬆️⬆️ Add these lines ⬆️⬆️⬆️ --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build the app and register the extension again. There's another Content Security Policy-related error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-16-ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-16-ko.png" alt="Chrome Extension pop-up error #3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the hash value mentioned in error to &lt;code&gt;manifest.json&lt;/code&gt; to fix this error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Getting Started Example (Blazor WASM)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Build an Extension!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"content_security_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval' 'sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA=' 'sha256-DnTH4SKCYpHBGu1OxDOqoYLsvmZTiYIWJVQ1Ava7Kig=' 'sha256-b9roSuk6Pa7l0Hl/LWXGQlupw8fMc6ME2+82/N3qM0Q='; object-src 'self'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What are those hash values?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sha256-DnTH4SKCYpHBGu1OxDOqoYLsvmZTiYIWJVQ1Ava7Kig=&lt;/code&gt;: It points to &lt;code&gt;popup.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sha256-b9roSuk6Pa7l0Hl/LWXGQlupw8fMc6ME2+82/N3qM0Q=&lt;/code&gt;: It points to &lt;code&gt;options.js&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once added, register the extension again. Now it's working as expected!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-17-ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-17-ko.png" alt="Chrome Extension to change background colour #2"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-18-ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F07%2Flift-and-shift-existing-chrome-extension-to-blazor-wasm-18-ko.png" alt="Chrome Extension that has changed the background colour #2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your existing JavaScript-based Chrome extension has now successfully been migrated to Blazor WebAssembly! If you want to see the complete example, go to &lt;a href="https://github.com/devkimchi/blazor-wasm-chrome-extension/tree/the-migration/src/ChromeExtensionV2" rel="noopener noreferrer"&gt;this page&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;So far, we've migrated the JavaScript-based Chrome extension app to &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor WebAssembly&lt;/a&gt; with minimal code changes. Both extensions are almost identical, except for a couple of points. In other words, your extension can easily be migrated using the "lift &amp;amp; shift" approach. However, it doesn't entirely make use of the powerful &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;JS interop&lt;/a&gt; feature yet. In the &lt;a href="https://dev.to/dotnet/chrome-extension-with-blazor-wasm-the-integration-5gi2"&gt;next post&lt;/a&gt;, let's explore how we can use the JS interop feature more for this migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you want to know more about Blazor?
&lt;/h2&gt;

&lt;p&gt;Here are some tutorials for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/learn/aspnet/blazor-tutorial/intro?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/learn/paths/build-web-apps-with-blazor/?WT.mc_id=dotnet-70466-juyoo" rel="noopener noreferrer"&gt;Blazor Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>blazorwasm</category>
      <category>browserextension</category>
      <category>migration</category>
    </item>
    <item>
      <title>Efficient OAuth Authorisation Management in Azure API Management</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Wed, 08 Jun 2022 00:55:17 +0000</pubDate>
      <link>https://forem.com/azure/efficient-oauth-authorisation-management-in-azure-api-management-3099</link>
      <guid>https://forem.com/azure/efficient-oauth-authorisation-management-in-azure-api-management-3099</guid>
      <description>&lt;p&gt;While developing an application, the majority of the time, you use API calls to send and receive messages. Therefore, you should follow an authentication and authorisation process to use the API unless it's the public API. Those authentication/authorisation process uses either 1) an auth key or 2) an access token through an OAuth process.&lt;/p&gt;

&lt;p&gt;Using the auth key approach, you can store it in a safe place like the &lt;a href="https://docs.microsoft.com/azure/key-vault/general/basic-concepts?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Azure Key Vault&lt;/a&gt; service and fetch the key. It's a relatively simple process. However, if you need to use the OAuth process, it gets complicated with the following authorisation process. Here's the conceptual OAuth process.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request an authorisation code&lt;/li&gt;
&lt;li&gt;Receive the authorisation code&lt;/li&gt;
&lt;li&gt;Request an access token by providing the authorisation code&lt;/li&gt;
&lt;li&gt;Receive the access token and a refresh token&lt;/li&gt;
&lt;li&gt;Call API request by providing the access token&lt;/li&gt;
&lt;li&gt;Receive the API response&lt;/li&gt;
&lt;li&gt;Request a new access token by providing the refresh token once the existing access token expires&lt;/li&gt;
&lt;li&gt;Receive the new access token and a new refresh token&lt;/li&gt;
&lt;li&gt;Call API request by providing the new access token&lt;/li&gt;
&lt;li&gt;Receive the API response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-01.png" alt="Conceptual OAuth Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to know more about the OAuth auth process, please refer to &lt;a href="https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-auth-code-flow?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;this doc&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Therefore, developers should implement those processes as a part of the application development. You might be lucky if the service you want to use offers an SDK. If not, you should do it all by yourself, which is very cumbersome. What if someone does all the tedious steps for you? Let's say, within the secure place, someone does all the auth process on your behalf and simply returns the access token. If this happens, your application development velocity will increase significantly. &lt;a href="https://docs.microsoft.com/azure/api-management/api-management-key-concepts?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Azure API Management (APIM)&lt;/a&gt; has recently released a &lt;a href="https://docs.microsoft.com/azure/api-management/authorizations-overview?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;preview feature called "Authorisations"&lt;/a&gt; that does the OAuth process on your behalf. Throughout this post, I'm going to discuss this feature using a &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Blazor Web Assembly (WASM) app&lt;/a&gt; hosted on &lt;a href="https://docs.microsoft.com/azure/static-web-apps/overview?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Azure Static Web Apps (SWA)&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can download the sample app code from this GitHub repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/aaronpowell" rel="noopener noreferrer"&gt;
        aaronpowell
      &lt;/a&gt; / &lt;a href="https://github.com/aaronpowell/token-store-demo" rel="noopener noreferrer"&gt;
        token-store-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A sample of how to use token store from JavaScript and .NET
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Token Store: Azure API Management Authorizations&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;For Blazor app: &lt;a href="https://dotnet.microsoft.com/download/dotnet/6.0?WT.mc_id=dotnet-57408-juyoo" rel="nofollow noopener noreferrer"&gt;.NET SDK 6.0.300 or later&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;For React app: &lt;a href="https://nodejs.org/en/download/" rel="nofollow noopener noreferrer"&gt;node.js v14 or later&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;GitHub Secrets&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Add the following GitHub Secrets to your repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AZURE_CREDENTIALS&lt;/code&gt;: Azure login credentials to get APIM and SWA details. To get this value, refer to &lt;a href="https://github.com/Azure/login#configure-deployment-credentials" rel="noopener noreferrer"&gt;this doc&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GH_PAT&lt;/code&gt;: GitHub personal access token to interact with GitHub resources. To get this value, refer to &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-token" rel="noopener noreferrer"&gt;this doc&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Dropbox App&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;This sample uses the access token issued by &lt;a href="https://dropbox.com" rel="nofollow noopener noreferrer"&gt;Dropbox&lt;/a&gt;. You need to &lt;a href="https://dropbox.com/developers/apps" rel="nofollow noopener noreferrer"&gt;create an app&lt;/a&gt; for this demo.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Autopilot&lt;/h3&gt;

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This will work only if this repo goes public.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click the button below to create and deploy both Blazor WASM app and React app in one go. Note the resource name for later use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://portal.azure.com/?Microsoft_Azure_ApiManagement=tuanguye2&amp;amp;feature.tokenstores=true#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Faaronpowell%2Ftoken-store-demo%2Fmain%2Fsrc%2Fbackend%2Fmain.json" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2F1-CONTRIBUTION-GUIDE%2Fimages%2Fdeploytoazure.svg%3Fsanitize%3Dtrue" alt="Deploy To Azure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once completing the resource provisioning above, run the GitHub Action workflow. Make sure to use the same resource name as…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aaronpowell/token-store-demo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are two apps in the repository – one based on &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Blazor WASM&lt;/a&gt; and the other based on React. I'm going to use the Blazor WASM sample here. If you're interested in the React app sample, please visit my colleague, &lt;a href="https://twitter.com/slace" rel="noopener noreferrer"&gt;Aaron Powell&lt;/a&gt;'s &lt;a href="https://techcommunity.microsoft.com/t5/apps-on-azure-blog/implementing-a-token-store-with-apim-authorizations/ba-p/3453516?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor Web Assembly App
&lt;/h2&gt;

&lt;p&gt;Please refer to the &lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Blazor Tutorial&lt;/a&gt; document for the Blazor WASM in general. Instead, I'm going to take a look at a component taking care of the OAuth authorisation and access token. This component takes the user inputs, stores them as a .csv file format and uploads it to DropBox. The Razor code below is nothing special but contains a form for user inputs. When a user completes the form and clicks the "&lt;strong&gt;Submit&lt;/strong&gt;" button, the &lt;code&gt;OnFormSubmittedAsync&lt;/code&gt; event is triggered (line #3).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="container-sm" style="max-width: 540px;"&amp;gt;
  &amp;lt;h1&amp;gt;Blazor Lead Capture&amp;lt;/h1&amp;gt;
  &amp;lt;form class="clearfix" @onsubmit="OnFormSubmittedAsync"&amp;gt;
    &amp;lt;fieldset&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label for="firstName" class="form-label"&amp;gt;First name&amp;lt;/label&amp;gt;
        &amp;lt;input type="text" class="form-control" id="firstName" name="firstName" placeholder="Justin" value="@userInfo.FirstName" @onchange="@(e =&amp;gt; OnFieldChanged(e, "firstName"))" /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label for="lastName" class="form-label"&amp;gt;Last name&amp;lt;/label&amp;gt;
        &amp;lt;input type="text" class="form-control" id="lastName" name="lastName" placeholder="Yoo" value="@userInfo.LastName" @onchange="@(e =&amp;gt; OnFieldChanged(e, "lastName"))" /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/fieldset&amp;gt;

    &amp;lt;fieldset&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label htmlFor="email" class="form-label"&amp;gt;Email&amp;lt;/label&amp;gt;
        &amp;lt;input type="email" class="form-control" id="email" name="email" placeholder="bar@email.com" value="@userInfo.Email" @onchange="@(e =&amp;gt; OnFieldChanged(e, "email"))" /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label htmlFor="phone" class="form-label"&amp;gt;Phone&amp;lt;/label&amp;gt;
        &amp;lt;input type="phone" class="form-control" id="phone" name="phone" placeholder="555-555-555" value="@userInfo.Phone" @onchange="@(e =&amp;gt; OnFieldChanged(e, "phone"))" /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/fieldset&amp;gt;

    &amp;lt;fieldset&amp;gt;
      &amp;lt;button type="submit" class="btn btn-@componentUIInfo.ButtonColour" disabled="@(componentUIInfo.Submitting || string.IsNullOrWhiteSpace(userInfo.FirstName) || string.IsNullOrWhiteSpace(userInfo.LastName) || string.IsNullOrWhiteSpace(userInfo.Email) || string.IsNullOrWhiteSpace(userInfo.Phone))"&amp;gt;
        &amp;lt;span&amp;gt;Submit&amp;lt;/span&amp;gt;
        &amp;lt;span class="spinner-border spinner-border-sm" style="display:@componentUIInfo.DisplaySpinner;" role="status" aria-hidden="true"&amp;gt;&amp;lt;/span&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/fieldset&amp;gt;
  &amp;lt;/form&amp;gt;

  &amp;lt;div class="alert alert-@componentUIInfo.AlertResult" style="display:@componentUIInfo.DisplayResult;"&amp;gt;
    &amp;lt;h2&amp;gt;@componentUIInfo.MessageResult&amp;lt;/h2&amp;gt;
    &amp;lt;button type="reset" class="btn btn-dark" @onclick="ResetFields"&amp;gt;
      &amp;lt;span&amp;gt;Start Over?&amp;lt;/span&amp;gt;
    &amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following &lt;code&gt;@code { ... }&lt;/code&gt; block is the C# code interacting with the Razor component. For brevity, I've left two methods. The first method is the event handler, &lt;code&gt;OnFormSubmittedAsync&lt;/code&gt; triggered by the "&lt;strong&gt;Submit&lt;/strong&gt;" button. Then, inside the event handler, it calls the &lt;code&gt;SaveToDropboxAsync&lt;/code&gt; method, which takes care of all the things, including access token fetch and DropBox save.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnFormSubmittedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;SaveToDropboxAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;SaveToDropboxAsync&lt;/code&gt; method, it firstly gets the environment variable of &lt;code&gt;APIM_Endpoint&lt;/code&gt; (line #4). Blazor WASM stores all the environment variables in the &lt;code&gt;appsettings.json&lt;/code&gt; file, which I will discuss later. By calling the APIM endpoint from &lt;code&gt;appsettings.json&lt;/code&gt;, the app gets the access token to DropBox (line #7), and the DropBox client instance uploads the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SaveToDropboxAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Gets the APIM endpoint from appsettings.json&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;requestUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"APIM_Endpoint"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Gets the auth token from APIM&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Builds contents.&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"/submissions/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"yyyyMMddHHmmss"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;.csv"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Phone&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UTF8Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Uploads the contents.&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FileMetadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dropbox&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DropboxClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dropbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UploadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WriteMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Overwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a look at the &lt;code&gt;appsettings.json&lt;/code&gt; file that contains the &lt;code&gt;APIM_Endpoint&lt;/code&gt; environment variable. The value is the APIM endpoint to get the DropBox access token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"APIM_Endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;APIM_NAME&amp;gt;.azure-api.net/dropbox-demo/token?subscription-key=&amp;lt;APIM_SUBSCRIPTION_KEY&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the Blazor WASM app on your local machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, open your web browser and enter the URL of &lt;code&gt;https://localhost:5001&lt;/code&gt;, and you will see the page like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-02.png" alt="Blazor WASM Landing Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill out the form and click the "&lt;strong&gt;Submit&lt;/strong&gt;" button, and the app will save the form details to DropBox. Then, if you open the Developer Tools of your web browser, you can see how the Blazor WASM app calls the APIM endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-03.png" alt="Request Access Token #1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the API call returns the access token to DropBox.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-04.png" alt="Request Access Token #2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this access token, you can create and store a file. Here's the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-05.png" alt="Dropbox Upload Result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are no codes for OAuth authorisation to DropBox, as you can recall. Instead, it starts from the API call that directly gets the access token. So how can the code remove all the preflight codes before getting the access token? This is the new APIM authorisation feature being referred to in this post. In short, the APIM instance internally performs all the OAuth related processes on behalf of the Blazor WASM app and simply returns the access token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure API Management Instance
&lt;/h2&gt;

&lt;p&gt;Let's dig into the new APIM feature. You can click the button below to provision all the resources onto Azure at once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Faaronpowell%2Ftoken-store-demo%2Fmain%2Fsrc%2Fbackend%2Fmain.json" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2F1-CONTRIBUTION-GUIDE%2Fimages%2Fdeploytoazure.svg%3Fsanitize%3Dtrue" alt="Deploy To Azure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alternatively, you can run the bicep files declared below. Let's take a further look at the bicep files. First, declare the APIM instance. You might notice that it enables the &lt;a href="https://docs.microsoft.com/azure/api-management/api-management-howto-use-managed-service-identity?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Managed Identity&lt;/a&gt; feature (line #13-15), which I'll discuss later in this post. For convenience, name the APIM instance of &lt;code&gt;token-store-demo-apim&lt;/code&gt; and set the location to &lt;code&gt;West Central US&lt;/code&gt;. The resource group for this provision is set to &lt;code&gt;rg-token-store-demo&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// APIM instance&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="nx"&gt;apim&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Microsoft.ApiManagement/service@2021-08-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token-store-demo-apim&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;westcentralus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Developer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nl"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;publisherName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;publisherEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;john.doe@nomail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nl"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SystemAssigned&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is the CORS policy because Azure SWA directly calls the APIM endpoint. Within the &lt;code&gt;inbound&lt;/code&gt; node, add the &lt;code&gt;cors&lt;/code&gt; node, add the &lt;code&gt;allowed-origins&lt;/code&gt; node under it, and add the &lt;code&gt;origin&lt;/code&gt; node with its value of &lt;code&gt;*&lt;/code&gt;. Make sure that it's just for the demo purpose. You should add the specific URL for better security if it goes live.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Service Policy&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="nx"&gt;apim_policy&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Microsoft.ApiManagement/service/policies@2021-08-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apim&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;policy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;service_policy&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Service Policy Definition&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;service_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'''&lt;/span&gt;&lt;span class="s1"&gt;
&amp;lt;policies&amp;gt;
    &amp;lt;inbound&amp;gt;
        &amp;lt;cors allow-credentials="false"&amp;gt;
            &amp;lt;allowed-origins&amp;gt;
                &amp;lt;origin&amp;gt;*&amp;lt;/origin&amp;gt;
            &amp;lt;/allowed-origins&amp;gt;
            &amp;lt;allowed-methods&amp;gt;
                &amp;lt;method&amp;gt;GET&amp;lt;/method&amp;gt;
                &amp;lt;method&amp;gt;POST&amp;lt;/method&amp;gt;
            &amp;lt;/allowed-methods&amp;gt;
        &amp;lt;/cors&amp;gt;
    &amp;lt;/inbound&amp;gt;
    &amp;lt;backend&amp;gt;
        &amp;lt;forward-request /&amp;gt;
    &amp;lt;/backend&amp;gt;
    &amp;lt;outbound /&amp;gt;
    &amp;lt;on-error /&amp;gt;
&amp;lt;/policies&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After declaring the APIM instance, define an API and its operation. The API's &lt;code&gt;serviceUrl&lt;/code&gt; is set to the base URL of the DropBox API, and the operation endpoint is set to &lt;code&gt;/token&lt;/code&gt; that returns the access token. Therefore, the entire APIM endpoint might look like &lt;code&gt;https://token-store-demo-apim.azure-api.net/dropbox-demo/token&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// API&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Microsoft.ApiManagement/service/apis@2021-08-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dropbox-demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apim&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;serviceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.dropboxapi.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dropbox-demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dropbox-demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Operation&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="nx"&gt;api_gettoken&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Microsoft.ApiManagement/service/apis/operations@2021-08-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gettoken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;urlTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gettoken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this endpoint doesn't exist on DropBox API. Therefore, add the operation policy to make this operation work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Operation Policy&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="nx"&gt;api_gettoken_policy&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Microsoft.ApiManagement/service/apis/operations/policies@2021-08-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api_gettoken&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;policy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;operation_token_policy&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following XML document of the operation policy explains the core idea of this APIM's new feature. Add the &lt;code&gt;get-authorization-context&lt;/code&gt; node under &lt;code&gt;inbound&lt;/code&gt;. It has the following attributes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;provider-id&lt;/code&gt;: &lt;code&gt;dropbox-demo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;authorization-id&lt;/code&gt;: &lt;code&gt;auth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;context-variable-name&lt;/code&gt;: &lt;code&gt;auth-context&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;identity-type&lt;/code&gt;: &lt;code&gt;managed&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, the APIM instance has enabled the Managed Identity feature corresponding to &lt;code&gt;identity-type&lt;/code&gt;. Both &lt;code&gt;provider-id&lt;/code&gt; and &lt;code&gt;authorisation-id&lt;/code&gt; will be used later. The &lt;code&gt;context-variable-name&lt;/code&gt; attribute is set to &lt;code&gt;auth-context&lt;/code&gt;. It's used in the &lt;code&gt;return-response&lt;/code&gt; node that holds the access token value. Overall, this operation policy takes care of getting access token on behalf of the SWA app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Operation Token Policy Definition&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;operation_token_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'''&lt;/span&gt;&lt;span class="s1"&gt;
&amp;lt;policies&amp;gt;
    &amp;lt;inbound&amp;gt;
        &amp;lt;base /&amp;gt;
        &amp;lt;get-authorization-context provider-id="dropbox-demo" authorization-id="auth" context-variable-name="auth-context" ignore-error="false" identity-type="managed" /&amp;gt;
        &amp;lt;return-response&amp;gt;
            &amp;lt;set-body&amp;gt;@(((Authorization)context.Variables.GetValueOrDefault(&amp;amp;quot;auth-context&amp;amp;quot;))?.AccessToken)&amp;lt;/set-body&amp;gt;
        &amp;lt;/return-response&amp;gt;
    &amp;lt;/inbound&amp;gt;
    &amp;lt;backend&amp;gt;
        &amp;lt;base /&amp;gt;
    &amp;lt;/backend&amp;gt;
    &amp;lt;outbound&amp;gt;
        &amp;lt;base /&amp;gt;
    &amp;lt;/outbound&amp;gt;
    &amp;lt;on-error&amp;gt;
        &amp;lt;base /&amp;gt;
    &amp;lt;/on-error&amp;gt;
&amp;lt;/policies&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You've got the APIM instance defined. Now, you need the SWA app instance for the Blazor WASM app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Static Web Apps Instance
&lt;/h2&gt;

&lt;p&gt;To host the Blazor WASM app, you need to provision an &lt;a href="https://docs.microsoft.com/azure/static-web-apps/overview?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Azure SWA&lt;/a&gt; instance. Here's the bicep code for it. First, give the app name &lt;code&gt;token-store-demo-blazor-swa&lt;/code&gt; and the location of &lt;code&gt;Central US&lt;/code&gt; under the resource group of &lt;code&gt;rg-token-store-demo&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SWA instance&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="nx"&gt;sttapp&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Microsoft.Web/staticSites@2021-02-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token-store-demo-blazor-swa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;centralus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Free&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nl"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allowConfigFileUpdates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;stagingEnvironmentPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Blazor WASM App Deployment to Azure Static Web Apps Instance
&lt;/h2&gt;

&lt;p&gt;You've got the new ASWA instance. It's time to deploy the Blazor WASM app, which is located in the &lt;code&gt;src/frontend/blazor&lt;/code&gt; directory. The first step should be adding the &lt;code&gt;appsettings.json&lt;/code&gt; file that contains the APIM endpoint for the access token. The following commands are how to get the APIM's endpoint URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get APIM gateway URL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;rg_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rg-token-store-demo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;apim_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token-store-demo-apim&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;gateway_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;apim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$rg_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$apim_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gatewayUrl"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tsv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Get APIM subscription key&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;subscription_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tsv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;apim_secret_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;/subscriptions/&lt;/span&gt;&lt;span class="nv"&gt;$subscription_id&lt;/span&gt;&lt;span class="nx"&gt;/resourceGroups/&lt;/span&gt;&lt;span class="nv"&gt;$rg_name&lt;/span&gt;&lt;span class="nx"&gt;/providers/Microsoft.ApiManagement/service/&lt;/span&gt;&lt;span class="nv"&gt;$apim_name&lt;/span&gt;&lt;span class="nx"&gt;/subscriptions/master/listSecrets&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="nt"&gt;-08-01&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;subscription_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$apim_secret_uri&lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;api-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$api_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.primaryKey'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Build APIM endpoint&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;apim_endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$gateway_url&lt;/span&gt;&lt;span class="n"&gt;/dropbox-demo/token\&lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;subscription-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$subscription_key&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The APIM's endpoint URL is finally landed in the &lt;code&gt;apim_endpoint&lt;/code&gt; variable. Therefore, rename the &lt;code&gt;appsettings.sample.json&lt;/code&gt; file under the &lt;code&gt;src/frontend/blazor/wwwroot&lt;/code&gt; directory to &lt;code&gt;appsettings.json&lt;/code&gt; and update the endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"APIM_Endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;apim_endpoint&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build and create the artifact of the Blazor WASM app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;restore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/src/frontend/blazor&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/src/frontend/blazor&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/src/frontend/blazor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/src/frontend/blazor/bin&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's one more step for deployment. Get the deployment key by running the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;swa_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;staticwebapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rg-token-store-demo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token-store-demo-blazor-swa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"properties.apiKey"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tsv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, deploy the Blazor WASM app by running the command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;swa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/src/frontend/blazor/bin/wwwroot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$swa_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although everything has gone perfectly so far, you can see the error below after submitting the form:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-06.png" alt="Internal Server Error"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's because you haven't made consent to the DropBox app yet. The final step will be consent.&lt;/p&gt;

&lt;h2&gt;
  
  
  DropBox App Consent
&lt;/h2&gt;

&lt;p&gt;Create a DropBox app by following &lt;a href="https://www.dropbox.com/developers/reference/getting-started#app%20console" rel="noopener noreferrer"&gt;this document&lt;/a&gt;, and you will get both &lt;code&gt;App key&lt;/code&gt; and &lt;code&gt;App secret&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-07.png" alt="Dropbox App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need both values for the consent within the APIM instance. Click the "&lt;strong&gt;Authorisations (preview)&lt;/strong&gt;" at the blade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-08.png" alt="Authorizations Preview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are no authorisation apps yet. Therefore, click the "&lt;strong&gt;Create&lt;/strong&gt;" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-09.png" alt="Authorizations Preview Pane"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can recall how you set up the &lt;code&gt;get-authorisation-context&lt;/code&gt; node for the operation policy. It's time to use them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;dropbox-demo&lt;/code&gt; to the "&lt;strong&gt;Provider name&lt;/strong&gt;" field.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;DropBox&lt;/code&gt; from the "&lt;strong&gt;Identity provider&lt;/strong&gt;" field.&lt;/li&gt;
&lt;li&gt;Enter DropBox's app key value to the "&lt;strong&gt;Client id&lt;/strong&gt;" field.&lt;/li&gt;
&lt;li&gt;Enter DropBox's app secreet value to the "&lt;strong&gt;Client secret&lt;/strong&gt;" field.&lt;/li&gt;
&lt;li&gt;Enter &lt;code&gt;files.metadata.write files.content.write files.content.read&lt;/code&gt; to the "&lt;strong&gt;Scopes&lt;/strong&gt;" field.&lt;/li&gt;
&lt;li&gt;Enther &lt;code&gt;auth&lt;/code&gt; to the "&lt;strong&gt;Authorization name&lt;/strong&gt;" field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-10.png" alt="Create Authorization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "&lt;strong&gt;Create&lt;/strong&gt;" button and get the redirection URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-11.png" alt="Redirection URL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the redirection URL to the DropBox app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-12.png" alt="DropBox App Update"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back to the APIM instance and log in to the DropBox app. Then, proceed the pop-up window by clicking the "&lt;strong&gt;Continue&lt;/strong&gt;" and "&lt;strong&gt;Allow&lt;/strong&gt;" and "&lt;strong&gt;Allow access&lt;/strong&gt;" buttons consecutively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-13.png" alt="DropBox App Login on APIM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, you will see the success message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-14.png" alt="DropBox App Authorized"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The DropBox app has now been authorised. But the APIM instance has not yet been authorised to access the DropBox app. Since the APIM instance has the Managed Identity feature enabled, let's use it. Choose "&lt;strong&gt;Managed identity&lt;/strong&gt;" and click the "&lt;strong&gt;Add members&lt;/strong&gt;" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-15.png" alt="APIM Managed Identity"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Find the APIM instance and add it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-16.png" alt="Add APIM Managed Identity"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, both APIM and DropBox can communicate with each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-17.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-17.png" alt="APIM Managed Identity Added"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you see the DropBox app authorised?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-18.png" alt="DropBox Authorization Created"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's test the endpoint whether it actually works or not. Follow the menu "&lt;strong&gt;APIs&lt;/strong&gt;" ➡️ "&lt;strong&gt;dropbox-demo&lt;/strong&gt;" ➡️ "&lt;strong&gt;gettoken&lt;/strong&gt;" and click the "&lt;strong&gt;Send&lt;/strong&gt;" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-19.png" alt="APIM Test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you will see the access token successfully issued.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-20.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-20.png" alt="APIM Test Success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's get back to the ASWA app and fill out the form. There is no error this time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-21.png" alt="Static Web App to APIM Request Success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the uploaded file appears on DropBox!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F06%2Fapim-token-store-22.png" alt="DropBox File Created"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;So far, we've discussed the new OAuth authorisation feature of &lt;a href="https://docs.microsoft.com/azure/api-management/api-management-key-concepts?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Azure API Management&lt;/a&gt;. Ultimately, we need the access token through the OAuth process. APIM performs this process. Therefore, we can save a huge amount of time by not implementing this feature within our app. Since it's currently in preview, some features may be ready, but they will be continuously improved.&lt;/p&gt;

&lt;p&gt;If you want to know more about this APIM OAuth authorisation management feature, please visit the document below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/azure/api-management/authorizations-overview?WT.mc_id=dotnet-57408-juyoo" rel="noopener noreferrer"&gt;Azure API Management Authorisations Management&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>apimanagement</category>
      <category>authorizations</category>
      <category>blazorwasm</category>
    </item>
    <item>
      <title>Blazor WebAssembly for Headless CMS on Azure Static Web Apps</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Fri, 13 May 2022 00:00:33 +0000</pubDate>
      <link>https://forem.com/azure/blazor-webassembly-for-headless-cms-on-azure-static-web-apps-412c</link>
      <guid>https://forem.com/azure/blazor-webassembly-for-headless-cms-on-azure-static-web-apps-412c</guid>
      <description>&lt;p&gt;One of the most popular scenarios to build a static website is to run a blog site for myself or my organisation. WordPress is the most popular service for this purpose. Now, you want to migrate your WordPress blog site to a static website, but it doesn't look easy.&lt;/p&gt;

&lt;p&gt;What if you still want to use the WordPress site to write content but only want to refresh the UI outside the site? What if you can even use C# for it through &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Blazor WebAssembly&lt;/a&gt;? You are now able to use the existing WordPress site as the data source of truth and build a UI in a separate instance with your preferred method. Does that sound attractive?&lt;/p&gt;

&lt;p&gt;Throughout this post, I'm going to discuss how to use the serviced WordPress instance as the headless CMS, build the Blazor WebAssembly app to make use of the WordPress as a data source, and host it to &lt;a href="https://docs.microsoft.com/azure/static-web-apps/overview?WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can download the sample app code from this GitHub repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/justinyoo" rel="noopener noreferrer"&gt;
        justinyoo
      &lt;/a&gt; / &lt;a href="https://github.com/justinyoo/blazor-wasm-azfunc-aswa" rel="noopener noreferrer"&gt;
        blazor-wasm-azfunc-aswa
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blazor WASM with Azure Functions on ASWA for headless CMS
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Headless CMS Example with Blazor WebAssembly on Azure Static Web Apps&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This provides a sample headless CMS app using Blazor WebAssembly hosted on Azure Static Web Apps.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/download?WT.mc_id=dotnet-49043-juyoo&amp;amp;ocid=AID3035128" rel="nofollow noopener noreferrer"&gt;.NET 6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://visualstudio.microsoft.com/vs/?WT.mc_id=dotnet-49043-juyoo&amp;amp;ocid=AID3035128" rel="nofollow noopener noreferrer"&gt;Visual Studio 2022&lt;/a&gt; or &lt;a href="https://code.visualstudio.com/?WT.mc_id=dotnet-49043-juyoo&amp;amp;ocid=AID3035128" rel="nofollow noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/justinyoo/blazor-wasm-azfunc-aswa" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Serviced WordPress Site Instance
&lt;/h2&gt;

&lt;p&gt;I've got a serviced WordPress site that is no longer maintained but archived.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-01.png" alt="Old WordPress Blog Site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to use this site as a data source, I need to be able to access to the contents through APIs. Fortunately, WordPress provides full of HTTP API endpoints. Enter the following command in your terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET https://public-api.wordpress.com/rest/v1.1/sites/&amp;lt;site-name&amp;gt;.wordpress.com/posts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can use your preferred UI tool like Postman to get the list of contents:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-02.png" alt="Result from Wordpress API"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You get the contents. All you need to do now is to render the contents on the Blazore WASM app. There are two approaches to it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Directly call the API from the Blazor WASM app, or&lt;/li&gt;
&lt;li&gt;Call the API from the Blazor WASM app through the proxy API app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both approaches have pros and cons, but let's make it with the second approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxy API Application
&lt;/h2&gt;

&lt;p&gt;We don't need the proxy API app for &lt;a href="https://docs.microsoft.com/azure/static-web-apps/overview?WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt;. However, if you need to overcome the CORS issue, handle various APIs, or deal with some security concerns, using the proxy API is a good option. Because Azure Static Web Apps natively supports this proxy API feature, we can simply use it. First of all create an &lt;a href="https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=in-process%2Cfunctionsv2&amp;amp;pivots=programming-language-csharp&amp;amp;WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;HTTP trigger&lt;/a&gt; of &lt;a href="https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; app.&lt;/p&gt;

&lt;p&gt;Define &lt;code&gt;GetPosts&lt;/code&gt; for the WordPress API endpoint and create an &lt;code&gt;HttpClient&lt;/code&gt; instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostHttpTrigger&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;GetPosts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://public-api.wordpress.com/rest/v1.1/sites/{0}/posts"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you add the &lt;a href="https://aka.ms/azfunc-openapi" rel="noopener noreferrer"&gt;OpenAPI extension&lt;/a&gt; to your function app, your Blazor WASM app can easily access this proxy API. I'll handle this part later in this post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PostHttpTrigger"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;OpenApiOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"posts.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"posts"&lt;/span&gt; &lt;span class="p"&gt;})]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;OpenApiResponseWithBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bodyType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PostCollection&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"The OK response"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetPostsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the actual WordPress site name from the environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;requestUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetPosts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SITE__NAME"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, call the WordPress API and deserialise the result to the &lt;code&gt;PostCollection&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestUri&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeserializeObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PostCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the definitions of the &lt;code&gt;PostCollection&lt;/code&gt; class and its subclasses. Although the WordPress API returns a massive variety of results, we only need some of them defined in the &lt;code&gt;PostCollection&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostCollection&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PostItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Posts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;Metadata&lt;/span&gt; &lt;span class="n"&gt;Metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostItem&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PostId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt; &lt;span class="n"&gt;DatePublished&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"URL"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Excerpt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Author&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;AuthorId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"first_name"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"last_name"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Surname&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Metadata&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Links&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"next_page"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;NextPage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wpcom"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsWordpressCom&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, configure the &lt;code&gt;local.settings.json&lt;/code&gt; file. We set it to this file as we previously referred to the environment variable of &lt;code&gt;SITE__NAME&lt;/code&gt;. In addition to that, we set the CORS for the Blazor WebAssembly app integration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"IsEncrypted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AzureWebJobsStorage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UseDevelopmentStorage=true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"FUNCTIONS_WORKER_RUNTIME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dotnet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"SITE__NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;site-name&amp;gt;.wordpress.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CORS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All set! Run the proxy API app, and you will see the result like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-03.png" alt="Result from Proxy API"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The OpenAPI document URL to this proxy API app is one of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http://localhost:7071/api/swagger.json&lt;/code&gt; – OpenAPI V2&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http://localhost:7071/api/openapi/v2.json&lt;/code&gt; – OpenAPI V2&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http://localhost:7071/api/openapi/v3.json&lt;/code&gt; – OpenAPI V3&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Blazor WebAssembly Application
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://visualstudio.microsoft.com/vs/?WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt; and create a new Blazor WebAssembly project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-04.png" alt="Blazor WASM Project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give it the name of &lt;code&gt;WebApp&lt;/code&gt;, and leave the rest as default. Once the app is created, run the app by typing the F5 key, and you will see the screen like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-05.png" alt="Blazor WASM App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's time to connect to the proxy API to this Blazor app. First, make sure that the proxy API app is running background. Then, click the "&lt;strong&gt;Connected Services&lt;/strong&gt;" menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-06.png" alt="Connected Service Menu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click the "➕" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-07.png" alt="Add Reference"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the "&lt;strong&gt;OpenAPI&lt;/strong&gt;" option as a service reference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-08.png" alt="Choose OpenAPI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter the OpenAPI URL, give the namespace of &lt;code&gt;WebApp.Proxies&lt;/code&gt; and class name of &lt;code&gt;ProxyClient&lt;/code&gt;, and click the "&lt;strong&gt;Finish&lt;/strong&gt;" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-09.png" alt="Enter OpenAPI Details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see the proxy API is added to the Blazor app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-10.png" alt="OpenAPI Reference Added"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the three dots on the right-hand side and select the "&lt;strong&gt;View generated code&lt;/strong&gt;" menu. Can you see the auto-generated code from the OpenAPI document?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-11.png" alt="Auto-generated Code from OpenAPI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We just need to consume this code. First, open the &lt;code&gt;Program.cs&lt;/code&gt; file and add the dependency of the &lt;code&gt;ProxyClient&lt;/code&gt; instance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While developing this app on your local machine, the default base URL is &lt;code&gt;http://localhost:7071/api&lt;/code&gt;. But once it's deployed to Azure, it must follow the actual host address (line #8-12).&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Add these lines to inject the dependency of ProxyClient&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProxyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;/api"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By this far, your Blazor app is now freely accessible to the proxy API. So, let's implement this part. First, we simply modify the &lt;code&gt;index.razor&lt;/code&gt; page and render the list of blog posts in this post.&lt;/p&gt;

&lt;p&gt;Inject the dependency of &lt;code&gt;ProxyClient&lt;/code&gt; added through &lt;code&gt;Program.cs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Inject&lt;/span&gt; &lt;span class="n"&gt;ProxyClient&lt;/span&gt; &lt;span class="n"&gt;dependency&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;
&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;WebApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Proxies&lt;/span&gt;
&lt;span class="n"&gt;@inject&lt;/span&gt; &lt;span class="n"&gt;ProxyClient&lt;/span&gt; &lt;span class="n"&gt;Api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the HTML bits and pieces just below the &lt;code&gt;&amp;lt;SurveyPrompt&amp;gt;&lt;/code&gt; component that handles the list of blog posts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SurveyPrompt&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"How is Blazor working for you?"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nf"&gt;@if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="nc"&gt;table&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Excerpt&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;@foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;@post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;@post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"@post.URL"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;@post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;MarkupString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Excerpt&lt;/span&gt;&lt;span class="p"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the actual code to call the proxy API. Through this proxy API, this page receives the list of blog posts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PostItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnInitializedAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Posts_getAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's done! Run the Blazor app again. You can see the list of blog posts populated from Wordpress.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-12.png" alt="List of Blog Posts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need to push all the code to the GitHub repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Static Web Apps Hosting
&lt;/h2&gt;

&lt;p&gt;All coding parts have been completed! Now it's time to publish the app to &lt;a href="https://docs.microsoft.com/azure/static-web-apps/overview?WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt;. I'm not going to walk through this part. Instead, here are links to my previous posts about the deployment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/azure/07-deploying-static-web-apps-48e"&gt;Deploying Static Web Apps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you complete publishing your Blazor WebAssembly app with the proxy API app, go to the website to confirm whether everything is OK or not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsa0blogs.blob.core.windows.net%2Fdevkimchi%2F2022%2F05%2Fbuilding-headless-cms-with-blazor-wasm-13.png" alt="List of Blog Posts on Azure"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;So far, I've walked through how to build a &lt;a href="https://docs.microsoft.com/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-6.0&amp;amp;WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Blazor WebAssembly&lt;/a&gt; app with an &lt;a href="https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; proxy API and use serviced WordPress site as a data source. Also, I've published this app to &lt;a href="https://docs.microsoft.com/azure/static-web-apps/overview?WT.mc_id=dotnet-66600-juyoo" rel="noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt; instance. Although this post has only implemented the high-level concepts, it has basically touched almost everything you need. Therefore, it's up to you to use as many API endpoints as possible and make the UI prettier. Then, you can still use the existing WordPress site as your data source of truth.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>staticwebapps</category>
      <category>blazorwasm</category>
      <category>headlesscms</category>
    </item>
    <item>
      <title>Azure Apps Autopilot #2 - Deployment Script</title>
      <dc:creator>Justin Yoo</dc:creator>
      <pubDate>Thu, 07 Apr 2022 13:13:14 +0000</pubDate>
      <link>https://forem.com/azure/azure-apps-autopilot-2-deployment-script-4ehg</link>
      <guid>https://forem.com/azure/azure-apps-autopilot-2-deployment-script-4ehg</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/azure/azure-apps-autopilot-2ag8"&gt;previous post&lt;/a&gt;, we built the autopilot feature, using various event triggers from &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/azure/azure-resource-manager/bicep/overview?tabs=bicep&amp;amp;WT.mc_id=62548-dotnet-juyoo"&gt;Azure Bicep&lt;/a&gt;. Throughout this post, I'm going to build the revised autopilot feature using &lt;a href="https://docs.microsoft.com/azure/azure-resource-manager/bicep/deployment-script-bicep?WT.mc_id=62548-dotnet-juyoo"&gt;Azure Bicep Deployment Scripts resource&lt;/a&gt; without GitHub Actions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can download the sample code from this GitHub repository below.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/devkimchi"&gt;
        devkimchi
      &lt;/a&gt; / &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample"&gt;
        APIM-OpenAPI-Sample
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This provides sample Azure Functions apps that integrate with Azure API Management, and expose Swagger UI through it.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
APIM OpenAPI Integration Sample&lt;/h1&gt;
&lt;p&gt;This provides sample Azure Functions apps that integrate with Azure API Management, and expose their respective Swagger UI pages through it.&lt;/p&gt;
&lt;h2&gt;
Getting Started&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Click the button below to autopilot all instances and apps on Azure. Make sure that you don't forget the app name you give.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fdevkimchi%2FAPIM-OpenAPI-Sample%2Fmain%2FResources%2Fazuredeploy.json" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YmoRjqWz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg%3Fsanitize%3Dtrue" alt="Deploy To Azure"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visit the following URLs to check whether all the apps have been properly provisioned and deployed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://apim-&amp;lt;APP_NAME&amp;gt;.azure-api.net/azip/swagger/ui&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;&lt;code&gt;https://apim-&amp;lt;APP_NAME&amp;gt;.azure-api.net/azoop/swagger/ui&lt;/code&gt;&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
Known Issues&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Due to the fact that Azure CLI has an error to deploy .NET-based out-of-proc function app, the autopilot feature only takes care of the in-proc function app. However, manual deployment of both in-proc and out-of-proc function apps is fine.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/devkimchi/APIM-OpenAPI-Sample"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Solution Architecture
&lt;/h2&gt;

&lt;p&gt;Let's say you're building a microservices architecture. It typically consists of an API gateway and many API apps, which are &lt;a href="https://docs.microsoft.com/azure/api-management/api-management-key-concepts?WT.mc_id=62548-dotnet-juyoo"&gt;Azure API Management (APIM)&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=62548-dotnet-juyoo"&gt;Azure Functions&lt;/a&gt; API in this example. The microservices architecture might be more complex depending on the requirements, but it's way too far from our topic. Therefore, let's build a minimal structure that is working. Here's the diagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B4Uy1_Ko--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sa0blogs.blob.core.windows.net/devkimchi/2022/04/azure-bicep-deployment-script-01-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B4Uy1_Ko--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sa0blogs.blob.core.windows.net/devkimchi/2022/04/azure-bicep-deployment-script-01-en.png" alt="Microservices Architecture on Azure" width="880" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Resources Provisioning
&lt;/h2&gt;

&lt;p&gt;There are five resources to provision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/azure/storage/common/storage-account-overview?WT.mc_id=62548-dotnet-juyoo"&gt;Azure Storage&lt;/a&gt; 👉 &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/storageAccount.bicep"&gt;View Bicep code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview?WT.mc_id=62548-dotnet-juyoo"&gt;Application Insights&lt;/a&gt; 👉 &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/appInsights.bicep"&gt;View Bicep code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/azure/app-service/overview-hosting-plans?WT.mc_id=62548-dotnet-juyoo"&gt;App Service Plan&lt;/a&gt; 👉 &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/consumptionPlan.bicep"&gt;View Bicep code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=62548-dotnet-juyoo"&gt;Function App&lt;/a&gt; 👉 &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/functionApp.bicep"&gt;View Bicep code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/azure/api-management/api-management-key-concepts?WT.mc_id=62548-dotnet-juyoo"&gt;Azure API Management&lt;/a&gt; 👉 &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/apiManagement.bicep"&gt;View Bicep code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, each resource has its corresponding Bicep module. For example, it's required to provision Azure Storage, Application Insights and App Service Plan (Consumption) before creating an Azure Functions app. Therefore, the &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/provision-functionapp.bicep"&gt;&lt;code&gt;provision_functionapp.bicep&lt;/code&gt;&lt;/a&gt; file takes care of this orchestration. In addition to that, Azure API Management also needs Application Insights as a dependency, so the &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/provision-apimanagement.bicep"&gt;&lt;code&gt;provision_apimamagement.bicep&lt;/code&gt;&lt;/a&gt; file looks after this orchestration.&lt;/p&gt;

&lt;p&gt;After deploying the function app, the &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/provision-apimanagementapi.bicep"&gt;&lt;code&gt;provision_apimanagementapi.bicep&lt;/code&gt;&lt;/a&gt; registers the function app to APIM.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: I used the modularisation approach while writing the Bicep files. But it may differ from your situation, and writing one big Bicep file could be more efficient in some circumstances.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you complete writing the Bicep file, you might expect the following processes in that order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provision the function related resources 👉 &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/provision-functionapp.bicep"&gt;&lt;code&gt;provision_functionapp.bicep&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Provision the APIM related resources 👉 &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/provision-apimanagement.bicep"&gt;&lt;code&gt;provision_apimamagement.bicep&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;➡️ Deploy the function app ⬅️&lt;/li&gt;
&lt;li&gt;Integrate the function app with APIM 👉 &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/provision-apimanagementapi.bicep"&gt;&lt;code&gt;provision_apimanagementapi.bicep&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When #1 and #2 are over, you can see the following resources provisioned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I66XPU_u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sa0blogs.blob.core.windows.net/devkimchi/2022/04/azure-bicep-deployment-script-02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I66XPU_u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sa0blogs.blob.core.windows.net/devkimchi/2022/04/azure-bicep-deployment-script-02.png" alt="Azure Resource Provisioning" width="880" height="1180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the way, #4 can't be provisioned until the function app is deployed at #3. Moreover, #3 is not related to resource provisioning but app deployment. What if we can convert this app deployment experience into resource provisioning? Then, all processes from #1 to #4 can be done within the resource provisioning pipeline, which means the whole "autopilot" feature is set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Bicep – Deployment Scripts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/azure/azure-resource-manager/management/overview?WT.mc_id=62548-dotnet-juyoo"&gt;Azure Resource Manager (ARM)&lt;/a&gt; has introduced the concept of &lt;a href="https://docs.microsoft.com/azure/azure-resource-manager/bicep/deployment-script-bicep?WT.mc_id=62548-dotnet-juyoo"&gt;deployment script&lt;/a&gt;. Through this deployment script, ARM can include PowerShell scripts or bash scripts as a part of the resource provisioning pipeline. In other words, the deployment script resource can run &lt;a href="https://docs.microsoft.com/powershell/azure/what-is-azure-powershell?WT.mc_id=62548-dotnet-juyoo"&gt;Azure PowerShell&lt;/a&gt; or &lt;a href="https://docs.microsoft.com/cli/azure/what-is-azure-cli?WT.mc_id=62548-dotnet-juyoo"&gt;Azure CLI&lt;/a&gt;. How is that possible? Here's the Bicep file declaring the deployment script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="nx"&gt;ds&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Microsoft.Resources/deploymentScripts@2020-10-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-deployment-script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resourceGroup&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AzureCLI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserAssigned&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="na"&gt;userAssignedIdentities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;user-assigned-identity-id&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;azCliVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.33.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="na"&gt;containerSettings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;containerGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-container-group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nl"&gt;environmentVariables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;resource-name&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;primaryScriptUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;bash-script-url&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="na"&gt;retentionInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;P1D&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;kind&lt;/code&gt; attribute explicitly declares that it uses Azure CLI. You can declare "AzurePowerShell" if you like.&lt;/li&gt;
&lt;li&gt;The bash script needs the login credentials to run Azure CLI commands. Therefore, it uses the user-assigned identity.&lt;/li&gt;
&lt;li&gt;The bash script uses the Azure CLI of &lt;code&gt;v2.33.1&lt;/code&gt; in this example. It's recommended to use a specific version instead of the latest version, discussed later.&lt;/li&gt;
&lt;li&gt;The bash script uses the environment variable of &lt;code&gt;RESOURCE_NAME&lt;/code&gt;, which is declared and has the value assigned in the Bicep file.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;primaryScriptUri&lt;/code&gt; attribute declares the publicly accessible bash script URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The Bicep file only shows the necessary bits and pieces. If you want to know more details, please take a look at &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/deploymentScript.bicep"&gt;this Bicep file&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, what does this deployment script resource do? It temporarily provisions both &lt;a href="https://docs.microsoft.com/azure/container-instances/container-instances-overview?WT.mc_id=62548-dotnet-juyoo"&gt;Azure Container Instance&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/azure/storage/common/storage-account-overview?WT.mc_id=62548-dotnet-juyoo"&gt;Storage Account&lt;/a&gt; to handle the script. According to &lt;a href="https://docs.microsoft.com/azure/azure-resource-manager/bicep/deployment-script-bicep?WT.mc_id=62548-dotnet-juyoo"&gt;this document&lt;/a&gt;, using the Azure CLI version older than 30 days from the day running the script is recommended. Therefore, at the time of this writing, &lt;a href="https://docs.microsoft.com/cli/azure/release-notes-azure-cli?WT.mc_id=62548-dotnet-juyoo"&gt;&lt;code&gt;v2.33.1&lt;/code&gt;&lt;/a&gt; is the closest one. Of course, you can use the &lt;code&gt;az upgrade&lt;/code&gt; command within the script to get the latest version, but it's totally up to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Script – Bash Script
&lt;/h2&gt;

&lt;p&gt;Take a look at the bash script that runs the series of &lt;a href="https://docs.microsoft.com/cli/azure/what-is-azure-cli?WT.mc_id=62548-dotnet-juyoo"&gt;Azure CLI&lt;/a&gt; commands. Let's say the artifact name is &lt;code&gt;api.zip&lt;/code&gt;, and the following script gets the artifact URL stored in the GitHub repository at the beginning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get artifacts from GitHub&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-H&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Accept: application/vnd.github.v3+json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;https://api.github.com/repos/devkimchi/APIM-OpenAPI-Sample/releases/latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nx"&gt;jq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.assets[] | { name: .name, url: .browser_download_url }'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;apizip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$urls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'select(.name == "api.zip") | .url'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the artifact URL is set as the environment variable, &lt;code&gt;$apizip&lt;/code&gt;, Azure CLI uses this artifact URL to deploy the function app. The environment variable, &lt;code&gt;$RESOURCE_NAME&lt;/code&gt;, comes from the deployment script Bicep file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy function apps&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ipapp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;functionapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rg-&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fncapp-&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="nt"&gt;-api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--src-url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$apizip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we complete deployment, it should be registered to APIM through the OpenAPI document. How can we do that? Another Bicep file, &lt;code&gt;provision-apimanagementapi.bicep&lt;/code&gt;, can also be run within the bash script through Azure CLI. But make sure that, if you want to provision resources through URL, you MUST convert the Bicep file to an ARM template of the JSON type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Provision APIs to APIM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deployment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ApiManagement_Api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rg-&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c"&gt;# MUST be ARM template, not Bicep&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https://raw.githubusercontent.com/devkimchi/APIM-OpenAPI-Sample/main/Resources/provision-apimanagementapi.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we've got the bash script to run within the deployment script resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overall Resource Orchestration
&lt;/h2&gt;

&lt;p&gt;As the final step, everything written above should be composed into one. Here's the &lt;a href="https://github.com/devkimchi/APIM-OpenAPI-Sample/blob/main/Resources/main.bicep"&gt;&lt;code&gt;main.bicep&lt;/code&gt;&lt;/a&gt; file that orchestrates resources and deployment scripts. The deployment script resource should always come last after all other resources are provisioned by using the &lt;code&gt;dependsOn&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Provision API Management&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="nx"&gt;apim&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./provision-apimanagement.bicep&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ApiManagement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Provision function app&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="nx"&gt;fncapp&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./provision-functionapp.bicep&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FunctionApp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;apim&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="c1"&gt;// Provision deployment script&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="nx"&gt;uai&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./deploymentScript.bicep&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserAssignedIdentity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;apim&lt;/span&gt;
        &lt;span class="nx"&gt;fncapp&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is done, convert the last Bicep file into the ARM template and link it to the image button below. Then, just click the button and put in the necessary information. It will automatically provision resources, deploy apps and do the rest of the provisioning process within one single pipeline, and the app is ready to use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fdevkimchi%2FAPIM-OpenAPI-Sample%2Fmain%2FResources%2Fazuredeploy.json"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YmoRjqWz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg%3Fsanitize%3Dtrue" alt="Deploy To Azure" width="167" height="34"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you actually click the button above, you will be able to see the Azure Portal screen like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aFujNzRv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sa0blogs.blob.core.windows.net/devkimchi/2022/04/azure-bicep-deployment-script-03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aFujNzRv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sa0blogs.blob.core.windows.net/devkimchi/2022/04/azure-bicep-deployment-script-03.png" alt="Azure Portal Custom Template Provisioning" width="880" height="950"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did you complete all the steps? Then go to &lt;a href="https://docs.microsoft.com/azure/api-management/api-management-key-concepts?WT.mc_id=62548-dotnet-juyoo"&gt;APIM&lt;/a&gt; and visit one of the function app's Swagger UI endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yaLznRlZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sa0blogs.blob.core.windows.net/devkimchi/2022/04/azure-bicep-deployment-script-04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yaLznRlZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sa0blogs.blob.core.windows.net/devkimchi/2022/04/azure-bicep-deployment-script-04.png" alt="Azure Functions Swagger UI through APIM" width="880" height="353"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;So far, we've walked through the &lt;a href="https://docs.microsoft.com/azure/azure-resource-manager/bicep/overview?tabs=bicep&amp;amp;WT.mc_id=62548-dotnet-juyoo"&gt;Azure Bicep&lt;/a&gt;'s &lt;a href="https://docs.microsoft.com/azure/azure-resource-manager/bicep/deployment-script-bicep?WT.mc_id=62548-dotnet-juyoo"&gt;deployment script&lt;/a&gt; resources and revised the autopilot feature without needing to rely on &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;, as mentioned in the &lt;a href="https://dev.to/azure/azure-apps-autopilot-2ag8"&gt;previous post&lt;/a&gt;. Now, you can hand over your repository to your tech-sales representatives to demo for their clients or other dev units to take a look with no prior knowledge of your application set-up. How easy is that?&lt;/p&gt;

</description>
      <category>azure</category>
      <category>autopilot</category>
      <category>bicep</category>
      <category>devrel</category>
    </item>
  </channel>
</rss>
