Deploying Blazor WebAssembly applications to Azure Static Web Apps

Posted by: Damir Arh , on 5/12/2022, in Category ASP.NET Core
Views: 18744
Abstract: In this tutorial, I compare Azure Static Web Apps with Azure App Service and explain how to publish a Blazor WebAssembly application as a Static Web App.

Since the early days of Microsoft Azure, App Service has been the platform as a service (PaaS) of choice for hosting any type of web application or website. In May 2020, an alternative was made available when Azure Static Web Apps was first previewed. A year later, in May 2021, Azure Static Web Apps became generally available (GA) and thus fully supported.

If you decide to host a web application or website in Microsoft Azure, you should consider Azure Static Web Apps now as a viable alternative to the more established Azure App Service.

What are Azure Static Webs?

Azure Static Web Apps vs Azure App Service

Both are platform-as-a-service (PaaS) offerings. Yet, the two services are significantly different:

  • Azure App Service is a full-fledged application server on the cloud. It can run on both Windows and Linux and supports a variety of different payloads: .NET, Node.js, Python, and many others.
  • Azure Static Web Apps, on the other hand, can only serve static files (as the name suggests).

However, the restriction to static files isn’t so limiting for web applications. A large portion of today’s web applications and websites don’t need anything else:

  • Websites built with static site generators like Hugo, Jekyll, Gatsby, and others generate all the markup for their pages at build time, so you only need to serve static HTML, JavaScript, CSS, and asset files (images, fonts, etc.).
  • Single-page applications (SPAs) written in JavaScript (vanilla or one of the popular frameworks like Angular, React, and Vue) or other languages thanks to WebAssembly support in modern browsers (e.g., Blazor WebAssembly) run all their code in the browser, so again you only need to deploy static files to the hosting web server.
  • Even higher-level web frameworks like Next.js for React and Nuxt.js for Vue only need to serve their static files for two of their three modes of operation: single-page application and static site generation. Only server-side rendering requires Node.js code to run on the server and therefore cannot be hosted in Azure Static Web Apps.

You can read more about the difference between client-side and server-side rendered web apps in my DotNetCurry article: Architecture of web applications (with design patterns).

All of these apps (and many more) can of course be hosted in an App Service, so Azure Static Web Apps seems to be only a limited (albeit cheaper) alternative. This is contradicted by the fact that Azure Static Web Apps offer many additional services preconfigured:

  • Hosting a serverless backend API, implemented as Azure Functions in .NET Core, Node.js or Python.

You can learn more about Azure Functions in the DotNetCurry article by Gerald Versluis: Serverless Apps with Azure Functions.

  • A CI/CD pipeline for repositories in GitHub and (with some limitations) Azure DevOps.
  • Automatic deployment to a pre-production environment for each pull request created.
  • Globally distributed static files, as typically provided by CDN (Content Delivery Network) solutions.

01-azure-static-wepapps-components

Figure 1: Azure Static Web Apps components

Although all of these features can of course be implemented with an App Service, this would require manual configuration and/or the use of additional services at an additional cost. This can put Azure Static Web Apps at a distinct advantage if their features are a good fit for your needs.

 

Publishing a Blazor WebAssembly application

In the rest of this article, I will walk you through the process of publishing a Blazor WebAssembly application to Azure Static Web Apps and introduce you to its various features.

Blazor is Microsoft’s framework for building highly interactive web applications using . NET and C# instead of JavaScript (or TypeScript). It supports two different hosting models:

  • In the Blazor Server hosting model, the application runs in a .NET runtime environment on the web server. The output page is rendered there and sent to the client with full HTML markup. All user triggered events on the page are sent to the server with SignalR. There they are processed, and the resulting changes to the page are sent back to the client, again via SignalR. This model requires the server to manage all page state and have an open SignalR connection for each active client.
  • In the Blazor WebAssembly hosting model, the application runs in the browser like a normal JavaScript-based single-page application. The .NET runtime environment runs on top of the WebAssembly engine that is included in modern web browsers.

02-blazer-server-and-blazor-webassembly-hosting-models

Figure 2: Blazor Server and Blazor WebAssembly hosting models

Blazor Server applications cannot be hosted in Azure Static Web Apps because they require a . NET runtime on the server. Blazor WebAssembly applications, on the other hand, can be hosted in Azure Static Web Apps because the browser only needs to download static files from the server: assets, HTML, CSS, and .NET assemblies (instead of JavaScript files). This is what we will use in the next two sections.

You can learn more about Blazor WebAssembly in the DotNetCurry article from Daniel Jimenez Garcia: Using Blazor WebAssembly, SignalR and C# 9 to create full-stack real time applications. However, it’s worth noting that the application from this article cannot be hosted in Azure Static Web Apps, as it relies on an ASP.NET Web API backend.

Using the Blazor starter template from GitHub

There is not yet a project template in Visual Studio, which is pre-configured for publishing to Azure Static Web Apps.

If you are looking for a sample project to try with minimal effort, the Blazor Starter Application template on GitHub is your best choice. Provided you are logged in to GitHub with your account, you can use the template to initialize a new GitHub repository with its contents.

03-blazor-starter-application-template-in-github

Figure 3: Blazor Starter Application template in GitHub

The solution consists of three projects that provide you with a general guide on how to best structure your code:

  • Client is a Blazor WebAssembly application, which is very similar to the application created from the Blazor WebAssembly project template in Visual Studio. Only the data source for the weather forecast page is different: an Azure Function instead of a static JSON file.
  • Api is an Azure Functions project with a single function that returns the weather forecast data.
  • Shared is a .NET Standard class library with the WeatherForecast data type used by the other two projects.

04-weather-forecast-page-from-blazor-starter-application

Figure 4: Weather forecast page from the Blazor Starter Application

To run the application locally, clone the repository to your local machine and open it in your favorite code editor:

In Visual Studio 2019, select multiple startup projects: Api and Client, and then start the solution with or without debugging.

05-multiple-startup-projects-configuration-in-visual-studio-2019

Figure 5: Multiple startup projects configuration in Visual Studio 2019

In Visual Studio Code, select the Client/Server launch configuration from the dropdown and start debugging.

06-selecting-client-Server-launch-configuration-in-visual-studio-code

Figure 6: Selecting Client/Server launch configuration in Visual Studio Code

This ensures that the Azure Functions and Blazor WebAssembly run concurrently, just like when deploying the solution in Azure Static Web Apps.

Once you’ve verified that the application is running correctly, it’s time to publish it to Azure Static Web Apps. There aren’t too many options when creating a Static Web App resource in Azure Portal. The most important part is the Deployment details needed to enable the automatic CI/CD feature.

07-configuring-deployment-details-for-azure-static-web-app

Figure 7: Configuring the deployment details for an Azure Static Web App

If you select GitHub as the source for your application, you must first authorize access to your GitHub account and then select the repository and branch from which you want to deploy. There are a number of build presets for different frameworks that you can choose from. Blazor is one of them.

In general, you will need to further configure it with paths to different parts of your full solution, although the default settings match those in the Blazor Starter Application template:

  • App location is the path to the folder containing the Blazor WebAssembly project.
  • Api location is the path to the folder containing the Azure Functions project.
  • Output location is the path within the Blazor WebAssembly project that contains the final build output to be deployed to the web server.

Based on this information, a GitHub workflow file for GitHub Actions is created and committed to your repository. If you want to change any of the values later, you will need to modify this file. Fortunately, the configuration is well documented.

After the resource is created, a GitHub Action is automatically triggered to build and deploy the application. Until completed, a link will appear at the top of the resource page in Azure Portal that will take you to the GitHub page with details about the build in progress. The URL of your Azure Static Web App is also on this resource page, which you can click to test the application once it is deployed.

08-azure-static-web-app-resource-page

Figure 8: Azure Static Web App resource page in Azure Portal

If you select Other as the source for your app when creating the resource, no further configuration is possible on the Azure Portal. You must then manually configure the build and deployment process in your CI/CD tool.

Azure DevOps is the only other CI/CD tool that is officially supported with a readily available custom build task. However, there are no Azure Pipelines presets for Static Web Apps yet. You need to create a Starter pipeline and replace the default YAML with the following:

trigger:
  - main

pool:
  vmImage: ubuntu-latest

steps:
  - checkout: self
    submodules: true
  - task: AzureStaticWebApp@0
    inputs:
      app_location: '/Client'
      api_location: '/Api'
      output_location: '/wwwroot'
      azure_static_web_apps_api_token: $(deployment_token)

You may need to change the following values:

  • trigger contains the name of the branch in your repository from which you want to deploy.
  • app_location, api_location, and output_location are the same paths you can configure for GitHub repositories in Azure Portal.

Before you save and run the pipeline, you need to configure the deployment_token variable referenced in the last line of the YAML file. To get the value, click the Manage deployment token button on the toolbar of the Static Web App resource page in Azure Portal.

When creating the variable in Azure Pipelines, you should enable the option to keep the value secret.

09-configuring-deployment-token-variable-in-azure-pipelines

Figure 9: Configuring the deployment token variable in Azure Pipelines

When this pipeline is configured, the application from the Azure DevOps repository is automatically published to Azure Static Web Apps, just like the application from the GitHub repository.

Creating a custom solution for a Blazor WebAssembly application

Although creating a solution from the Blazor Starter Application template in GitHub makes deploying Azure Static Web Apps very easy, it’s not perfect and its limitations might bother you:

  • The template repository seems to be updated even less frequently than the project templates in Visual Studio 2019. For example, the Blazor WebAssembly app still uses version 3.2, so if you want to use version 5, you’ll have to update it yourself.
  • If you already have a Blazor WebAssembly app that you want to deploy to Azure Static Web Apps, it’s not easy to start with a completely new repository and migrate your app there, especially if you want to keep your Git history.

Fortunately, it’s not that difficult to deploy any Blazor WebAssembly application to Azure Static Web Apps using either GitHub or Azure DevOps. In this section, we’ll take a look at the steps required.

Deploying Blazor WebAssembly application to Azure Static Web Apps

Any existing application should be easy enough to deploy, as the Blazor WebAssembly application folder within the repository is fully configurable in the Azure Portal wizard or in the generated YAML file:

app_location: "dnc-swa-blazor"  # App source code path
api_location: "Api"             # Api source code path – optional
output_location: "wwwroot"      # Built app content directory - optional

Don’t worry if you don’t have an Azure Functions API project. If the build script doesn’t find it in the configured folder, it’ll simply skip this step.

However, after deployment, you’ll notice that the server responds with a 404 (missing page) error if you navigate directly to a non-root page of your Blazor WebAssembly application.

For example:

  • Opening the root page of your application and navigating to a subpage works.
  • Refreshing that subpage in your browser returns 404.

This is because there’s no static page on the web server that matches the URL of the subpage. When you navigate there from the root page in the browser, the Blazor WebAssembly application in the browser does the routing and makes it work. When you request the same page from the server, it doesn’t know which page to return.

You can fix this by adding a staticwebapp.config.json file with the following content to the root of your Blazor WebAssembly application folder:

{
  "navigationFallback": {
    "rewrite": "/index.html"
  }
}

This tells Azure Static Web Apps to serve the index.html page if there is no static page that matches the requested URL. The Blazor WebAssembly application in the browser will then ensure that the correct page is rendered once it is loaded.

If you experimented with Azure Static Web Apps during the preview, this file was originally named routes.json. The old name is still supported, but it is recommended that you use the new name instead.

If you want to add an Azure Functions API to your Blazor WebAssembly application, you need to make sure that .NET Core 3.1 (or alternatively Node.js 12 or Python 3.8) is used. Other runtime versions are not supported for Azure Functions, which are provided as part of Azure Static Web App. Don’t forget to properly configure the api_location in your project’s GitHub or Azure DevOps YAML file.

If you are using the Standard (non-free) hosting plan for Azure Static Web Apps, you can use another Azure Functions resource to host the API. However, you will have to deploy and pay for it separately.

Azure Static Web Apps use a reverse proxy to map the Azure Functions API to the /api subpath of your domain. One advantage is that you do not need to configure CORS (Cross-Origin Resource Sharing) to make Azure Functions work in the browser.

However, if the Azure Functions are run locally during development, they will usually be accessible at http://localhost:7071/api without remapping through a reverse proxy. This requires additional configuration to make it work.

When running locally, the Blazor WebAssembly application must use a different base path for API calls. In the Blazor Starter Application mentioned above, this is accomplished by a BaseAddress application configuration value in appsettings.Development.json file which is only in use during local development:

{
  "BaseAddress": http://localhost:7071/
}

When it is not present, the code falls back to the base URL of the application:

var baseAddress = builder.Configuration["BaseAddress"] ??
    builder.HostEnvironment.BaseAddress;
builder.Services.AddScoped(_ => new HttpClient
{
    BaseAddress = new Uri(baseAddress)
});

In addition, the Azure Functions application must be launched with the following arguments to disable CORS: start –cors *. You can configure this with a launchsettings.json file in the Properties folder of the Azure Functions project:

{
  "profiles": {
    "Api": {
      "commandName": "Project",
      "commandLineArgs": "start --cors *"
    }
  }
}

You can avoid all these special configurations for local development if you want to use Azure Static Web Apps CLI instead. It’s still in preview, but it already does a good job of emulating the same setup locally as when deploying the app.

To use it, you need to have Node.js installed. You can then install CLI with npm:

npm install -g @azure/static-web-apps-cli

From the root of your solution, you can launch CLI with the following command:

swa start http://localhost:5000 --api http://localhost:7071

For this to work, make sure you are running the Blazor WebAssembly application with Kestrel on port 5000. Otherwise, you will need to change the port in the command accordingly.

This command maps the Blazor WebAssembly application to http://localhost:4280 and the Azure Functions API to http://localhost:4280/api so that the same code can run both locally and in Azure after deployment. CLI can also read the configuration in staticwebapp.config.json to further emulate the behavior of Azure Static Web Apps after deployment.

Other Azure Static Web Apps features

There are other features in Azure Static Web Apps that I haven’t covered yet. Many of them can be configured via the staticwebapp.config.json file: URL redirects and rewriting, global and route-specific response headers, overrides for error pages, and more. Built-in support for user authentication with Azure Active Directory, GitHub and Twitter as identity providers is also available.

Pre-production environments

When deployed from GitHub, code from open pull requests is automatically deployed to a separate pre-production environment with its own URL that doesn’t affect the production version of the application published from the main branch in any way.

10-preproduction-url-in-github-pull-requested

Figure 10: Pre-production URL in GitHub pull request as published by the GitHub action

This simplifies testing of the code in the pull request. Whenever new code is pushed to the underlying branch, it’s automatically redeployed. When the pull request is merged (or closed), the pre-production environment is also automatically removed.

Unfortunately, this feature isn’t (yet) available when deploying from Azure DevOps.

Custom domains

Unlike App Service, which generates an azurewebsites.net subdomain that matches the resource name, Azure Static Web Apps don’t generate their URLs based on the name you used for the resource. Instead, they automatically generate a random subdomain at azurestaticapps.net (for example, delightful-cliff-06f9e7803.azurestaticapps.net).

For this reason, it’s even more important that you can configure a custom domain for the site without incurring any additional costs (other than the domain, of course). All you need to do is add a CNAME record with your DNS provider. Once this is validated, the configuration will be applied in Azure Portal.

A free SSL certificate will also be automatically created for this domain.

Advantages and disadvantages

You can create up to ten Azure Static Web Apps per Azure subscription for free (with some additional quotas) if you do not need SLA (Service Level Agreement). For most hobby projects, this should be acceptable.

If you need more reliability for your application, you can upgrade to the Standard plan where you are charged per application. In both cases, you get a lot of features: pre-configured CI/CD, CDN, custom domain, SSL certificates, etc. If you are used to App Services, this is a great value add.

On the other hand, Azure Static Web Apps have their own limitations:

  • Your code must preferably be in GitHub or at least Azure DevOps.
  • The route configuration is only flexible to a limited extent. Pattern-based redirects, for example, are not supported.
  • Most importantly, only a subset of applications is supported. Web pages cannot be generated on the server.

Any of these limitations could be a showstopper and force you to continue using App Service if you want to host your site in Azure PaaS.

Conclusion

This article introduced Azure Static Web Apps. It compares its features with Azure App Service, i.e. the other PaaS option for hosting websites in Azure. It describes the benefits and limitations of the new service.

The process of publishing and configuring a Static Web App is presented using a sample Blazor WebAssembly single-page application. To help you get started, common pitfalls and solutions to them are highlighted.

This article was technically reviewed by Daniel Jimenez Garcia.

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Damir Arh has many years of experience with software development and maintenance; from complex enterprise software projects to modern consumer-oriented mobile applications. Although he has worked with a wide spectrum of different languages, his favorite language remains C#. In his drive towards better development processes, he is a proponent of Test-driven development, Continuous Integration, and Continuous Deployment. He shares his knowledge by speaking at local user groups and conferences, blogging, and writing articles. He is an awarded Microsoft MVP for .NET since 2012.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!