Publish a Blazor Web App as Azure Static Web App

In 2018, the web framework, Blazor, was introduced. With Blazor, we can work on web UI with C# instead of JavaScript. Blazor can run the client-side C# code directly in the browser, using WebAssembly.

When server-side rendering is not required, we can then deploy our web app on platforms such as Azure Static Web App, a service that automatically builds and deploys full stack web apps to Azure from a code repository, such as GitHub.

In this article, I will share how the website for Singapore .NET Developers Community and Azure Community is re-built as a Blazor web app and deployed to Azure.

PROJECT GITHUB REPOSITORY

The complete source code of this project can be found at https://github.com/sg-dotnet/website.

Blazor Web UI

The community website is very simple. It is merely a single-page website with some descriptions and photos about the community. Then it also has a section showing list of meetup videos from the community YouTube channels.

We will build the website as Blazor WebAssembly App.

Firstly, we will have the index.html defined as follows. Please take note that the code snippet below uses CSS file which is not shown in this post. The complete and updated project can be viewed on the GitHub repo.

<!DOCTYPE html>
<html>

<head>
    <title>Singapore .NET Developers Community + Azure Community</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

    ...

    <link rel="icon" href="images/favicon.png" type="image/png">
    <link rel="stylesheet" href="css/main.css" />

    <base href="/" />
    http://_framework/blazor.webassembly.js
</head>

<body>
    <div id="app">
        <div style="position:absolute; top:30vh; width:100%; text-align:center">
            <h2>Welcome to dotnet.sg</h2>
            <div style="width: 50%; display: inline-block; height: 20px;">
                <div class="progress-line"></div>
            </div>
            
            <p>
                The website is loading...
            </p>
        </div>
    </div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <!-- Scripts -->
    http://javascript/jquery.min.js
</body>

</html>

Secondly, if we hope to have a similar UI template across all the web pages in the website, then we can define the HTML template under, for example, MainLayout.razor, as shown below. This template means that the header and footer sections can be shared across different web pages.

@inherits LayoutComponentBase

<!-- Header -->
<header id="header" class="alt">
    <div class="logo"><a href="/">SG <span>.NET + Azure Dev</span></a></div>
</header>

@Body

<!-- Footer -->
<footer id="footer">
    <div class="container">
        <ul class="icons">
           ...
        </ul>
    </div>
    <div class="copyright">
        &copy; ...
    </div>
</footer>

Finally, we simply need to define the @Body of each web page in their own Razor file, for example the Index.razor for the homepage.

In the Index.razor, we will fetch the data from a JSON file hosted on Azure Storage. The JSON file is periodically updated by Azure Function to fetch the latest video list from the YouTube channel of the community. Instead of using JavaScript, here we can simply write a C# code to do that directly on the Razor file of the homepage.

@code {
    private List<VideoFeed> videoFeeds = new List<VideoFeed>();

    protected override async Task OnInitializedAsync()
    {
        var allVideoFeeds = await Http.GetFromJsonAsync<VideoFeed[]>("...");

        videoFeeds = allVideoFeeds.ToList();
    }

    public class VideoFeed
    {
        public string VideoId { get; set; }

        public Channel Channel { get; set; }

        public string Title { get; set; }

        public string Description { get; set; }

        public DateTimeOffset PublishedAt { get; set; }
    }

    public class Channel
    {
        public string Name { get; set; }        
    }
}

Publish to Azure Static Web App from GitHub

We will have our codes ready in a GitHub repo with the following structure.

  • .github/workflows
  • DotNetCommunity.Singapore
    • Client
      • (Blazor client project here)

Next, we can proceed to create a new Azure Static Web App where we will host our website at. In the first step, we can easily link it up with our GitHub account.

We need to specify the deployment details for the Azure Static Web App.

After that, we will need to provide the Build details so that a GitHub workflow will be automatically generated. That is a GitHub Actions workflow that builds and publishes our Blazor web app. Hence, we must specify the corresponding folder paths within our GitHub repo, as shown in the screenshot below.

In the Build Details, we must setup the folder path correctly.

The “App location” is to point to the location of the source code for our Blazor web app. For the “Api location”, although we are not using it in our Blazor project now, we can still set it as follows so that in the future when we can easily setup the Api folder.

With this setup ready, whenever we update the codes in our GitHub repo via commits or pull requests, our Blazor web app will be built and deployed.

Our Blazor web app is being built in GitHub Actions.

Custom Domains

For the free version of the Azure Static web app, we are only allowed to have 2 custom domains per app. Another good news is that Azure Static Web Apps automatically provides a free SSL/TLS certificate for the auto-generated domain name and any custom domains we add.

CNAME record validation is the recommended way to add a custom domain, however, it only works for subdomains, such as “www.dotnet.sg” in our case.

For root domain, which is “dotnet.sg” in our case, by right we can do it in Azure Static Web App by using TXT record validation and an ALIAS record.

Take note that we can only create an ALIAS record if our domain provider supports it.

However, since there is currently no support of ALIAS or ANAME records in the domain provider that I am using, I have no choice but to have another Azure Function for binding “dotnet.sg”. This is because currently there is no IP address given in Azure Static Web App but there are IP address and Custom Domain Verification ID available in Azure Function. With these two information, we can easily map an A Record to our root domain, i.e. “dotnet.sg”.

Please take note that A Records are not supported for Consumption-based Function Apps. We must pay for the “App Service Plan” instead.

IP address and Custom Domain Verification ID on Azure Function. The root domain here is also SSL enabled.

After having the Azure Function ready, we need to perform URL redirect from “dotnet.sg” to “www.dotnet.sg”. With just a Proxy, we can create a Response Override with Status Code=302 and add a Header of Location=https://www.dotnet.sg, as shown in the following screenshot.

HTTP 302 on Azure Function Proxy.

With all these ready, we can finally get our community website up and running at dotnet.sg.

Welcome to the Singapore .NET/Azure Developer Community at dotnet.sg.

Export SSL Certificate For Azure Function

This step is optional. I need to go through this step because I have a Azure App Service managed certificate in one subscription but Azure Function in another subscription. Hence, I need to export the SSL certificate out and then import it back to another subscription.

We can export certificate from the Key Vault Secret.

In the Key Vault Secret screen, we then need to choose the correct secret version and download the certificate, as shown in the following screenshot.

Downloading the certificate as pfx.

After that, as mentioned in an online discussion about exporting and importing Azure App Service Certificate which has no password, we shall use tool such as OpenSSL to regenerate a pfx certificate with password that Azure Function can accept with the following commands.

> openssl pkcs12 -in .\old.pfx -out old.pem -nodes

> openssl pkcs12 -export -out .\new.pfx -in old.pem

We will be prompted for a password after executing the first command. We simply press enter to proceed because the certificate, as mentioned above, has no password.

OpenSSL command prompt.

With this step done, I finally can import the cert to the Azure Function in another subscription.

Yup, that’s all for hosting our community website as a Blazor web app on Azure Static Web App!

References

The code of this Blazor project described in this article can be found in our community GitHub repository: https://github.com/sg-dotnet/website.

Azure Cloud Service and SSL

I recently read an article written by Jeff Atwood on Coding Horror about whether we should encrypt all the traffic to our websites. I have a website which is utilizing external accounts with the help of .NET Identity, hence I must use HTTPs for my site before enabling users to login with their Facebook or Google accounts.

Purchase SSL Certificate from RapidSSL

My website is .NET web application with MVC 5 as front-end. It is also being hosted on Azure Cloud Service. For SSL certificate, I got it from RapidSSL.

RapidSSL Enrollment
RapidSSL Enrollment

Previously when I renewed the certificate for web application hosted on virtual machine, I could easily RDP to the virtual machine to configure its IIS settings. Now, the way to do it for Azure Cloud Service on the Azure Management Portal is a bit different.

Enter Certificate Signing Request (CSR)

In the process of purchasing SSL certificate on RapidSSL, I needed to submit CSR. To generate a CSR, what I did is just launching the IIS Manager on my Windows 8.1 machine. The process is pretty straightforward, as demonstrated on DigiCert website. The steps are as follows.

  1. Double click on “Server Certificates” feature of the local server;
  2. Under the “Action” panel, choose “Create Certificate Request…” link;
  3. Then there would be a window called “Distinguished Name Properties” popped out;
  4. Key in the correct information about the Common Name (which is the domain name of my website) and organization in the window;
  5. Choose “Microsoft RSA SChannel Cryptographic Provider” as the cryptographic service provider;
  6. Input “2048” as bit length.

CSR was generated successfully. I copied the generated text to RapidSSL textbox to continue the purchase.

Install SSL Certificate

After my payment went through, I received the certificate in text format via email from RapidSSL as shown below.

Web Server CERTIFICATE
----------------

-----BEGIN CERTIFICATE-----
<encoded data>
-----END CERTIFICATE-----

I then copied it to a text file and saved the file with the .cer extension.

Then, I went back to the IIS Manager on my computer. In the same Actions panel where I created the CSR, I then chose another option “Complete Certificate Request…”. In the new window, I provided the .cer file generated earlier.

Update Service Definition File

After that, in the Visual Studio Solution Window of my web project, I added a <Certificates> section, a new <InputEndpoint> for HTTPS, and a <Binding> element to map the HTTPS endpoint to my website within the WebRole section in the ServiceDefinition.csdef file.

<WebRole name="MyWebsiteWeb" vmsize="Medium">
    <Sites>
        <Site name="Web">
            <Binding>
                ...
                <Binding name="HTTPSEndpoint" endpointName="EndpointS" />
            </Bindings>
        </Site>
    </Sites>
    <Endpoints>
        ...
        <InputEndpoint name="EndpointS" protocol="https" port="443" certificate="SampleCertificate" />
    </Endpoints>
    ...
    <Certificates>
        <Certificate name="SampleCertificate" storeLocation="CurrentUser" storeName="My" />
    </Certificates>
</WebRole>

Update Service Configuration File

In addition, I edited the ServiceConfiguration.Cloud.cscfg file with one new <Certificates> section in the Role section.

<Role name="MyWebsiteWeb">
    ...
    <Certificates>
        ...
        <Certificate name="SampleCertificate" thumbprint="xxxxxx" thumbprintAlgorithm="xxx" />
    </Certificates>
</Role>

Both the thumbprint and thumbprintAlgorithm can be retrieved by double clicking on the .cer file.

Thumbprint and Its Algorithm
Thumbprint and its algorithm

Export Certificate as .pfx File

When I uploaded .cer file to Azure Management Portal, it couldn’t work. I had no idea why. Hence, I tried the alternative, which is using .pfx file. To do that, I first exported the certificate as .pfx file.

Firstly, I launched the Microsoft Management Console by running mmc.exe.

Export certificate from Microsoft Management Console.
Export certificate from Microsoft Management Console.

Secondly, I did the following steps to trigger the Certificate Export Wizard.

  1. File > Add/Remove Snap-in…
  2. Choose “Certificate” under “Available snap-ins” and then click “Add >”
  3. In the popup “Certificates snap-in” window, choose “Computer account”
  4. With the previous choice, I make snap-in to always manage in “Local computer”
  5. After clicking on the “Finish” button, I then click on the “Certificates” folder under “Personal” folder under “Certificates (Local Computer)” under the “Console Root”
  6. Right-click on the certificate that I want to export and choose export
  7. Finally the “Certificate Export Wizard” appears!

Finally, in the wizard, I followed the following steps to create a .pfx file of the certificate.

  1. Choose to export private key with the certificate
  2. Format will be Personal Information Exchange – PKCS #12 with all certificates in the certification path is included, if possible
  3. Enter a password to protect the private key
  4. Export

Certificate Export Wizard - Password and Private Key
Certificate Export Wizard – Password and Private Key

More detailed instructions can be found online, for example a page on Thawte about export a certificate from Microsoft IIS 7.

Upload Certificate to Azure

I then uploaded it to Microsoft Azure. It’s very simple. Just choose the cloud service and then upload the .pfx file (and enter the password used earlier for protecting the private key) to the certificate collection of the cloud service.

Upload certificate to Microsoft Azure in the Certificates tab.
Upload certificate to Microsoft Azure in the Certificates tab.

That’s all. It’s pretty straightforward, isn’t it?

If you would like to read more about Azure Cloud Service and SSL, please read the following articles which I find to be very useful.

Renewing SSL Certificate (GoDaddy + IIS 6)

I asked my friends about how to renew SSL certificate used on a Windows Server. Unfortunately, none of them really know how to do it on IIS 6. Hence, my senior decided to work together with me to renew our existing certificate on IIS as an experiment and learning opportunity.

We got our existing SSL certificate from GoDaddy. So, our first step is to visit the SSL Certificates section in the My Account page.

After that, in the Manage Certificate section of the selected certificate, we can submit new changes of our certificate. In order to renew the certificate, we submitted the new Certificate Signing Request (CSR) there.

Submit Certificate Changes - CSR
Submit Certificate Changes – CSR

CSR and Certificate Installation

So, where did we get the CSR from? From the wizard!

Firstly, we created a new website in IIS Manager. After that, we went to the Directory Security tab of the Properties of the website to create a new certificate. From there, we could get a new CSR.

Create New Certificate
Create New Certificate

Create New Certificate - Name and Encryption Strength
Create New Certificate – Name and Encryption Strength

Key in the Organization name which will be displayed on the SSL Certificate
Key in the Organization name which will be displayed on the SSL Certificate

Finally we got the certificate request file.
Finally we got the certificate request file.

Secondly, we went back to GoDaddy to submit the CSR.

Thirdly, we downloaded the certificates from GoDaddy after we submitted the CSR. With the certificates downloaded to the server, we just followed the instructions available on GoDaddy to install both the Primary SSL Certificate and Intermediate SSL Certificate.

Finally, we went to the IIS Web Site that we would like to have its SSL certificate to be renew and choose the “Replace the current certificate” option.

Replace the existing certificate with new certificate.
Replace the existing certificate with new certificate.

Done. It’s quite straightforward. Please tell me if I’m wrong or you have a better way of doing all these on IIS. Thanks in advance and happy new year! =)