Hosting a Blazor App in Azure Storage Static Websites
Thursday, June 28, 2018
In recent years, there's been a shift towards building applications with serverless architectures. Serverless backends are typically powered by fully managed and infinitely scalable services with consumption-based pricing, such as Azure Functions and Logic Apps.
Today, Azure Storage announced the public preview of its static website hosting feature that complements serverless backends by making it easy to also host frontend single page applications (SPAs) in a fully managed, highly scalable, consumption-based service. Blob Storage has always been able to serve static assets such as HTML, CSS, and JavaScript. The static websites feature adds more traditional web server capabilities that are required to host SPAs and statically generated websites, including support for defining default and error pages.
Blazor is an experimental framework from the ASP.NET team for building SPAs using .NET instead of JavaScript. It does this by running standard .NET DLLs in the browser on top of a Mono runtime that has been compiled to WebAssembly.
- Blog post announcing Azure Storage static websites
- Documentation on the static websites feature
- ASP.NET Blazor
In this post, we'll look at how to host a standalone Blazor application with no server-side code in a Azure Blob Storage static website.
Create a Storage account with static websites enabled
We'll start by creating a general purpose V2 Storage account from the portal.
After the Storage account is created, we can open it in the Azure portal. In the left navigation, we'll open the Static Websites (preview) feature, enable it, and specify index.html
as the index document name ("index.html" may already appear in the box as a placeholder, enter the value anyway).
When the settings are saved, the static websites feature is enabled and a primary endpoint is shown. This is the URL to our website.
Create a Blazor app
To create a Blazor application, we first have to install the Blazor project templates. If we're using Visual Studio 2017, we'll want to install the Visual Studio tooling for Blazor.
Instead of Visual Studio, today we'll be building our Blazor app with the .NET CLI. To install the Blazor templates, run this command:
$ dotnet new -i Microsoft.AspNetCore.Blazor.Templates
In an empty folder, we'll create a new standalone Blazor project.
$ dotnet new blazor
To build the static assets for the application, we'll use the dotnet publish
command:
$ dotnet publish -c Release -o out
This publishes the app to a folder named out.
Deploy the app to Blob Storage
Now we're ready to deploy the app. The assets for the app is in a folder named out/{project-name}/dist. To deploy it, we'll use the az storage blob upload-batch
Azure CLI command. The default static websites container is $web
; it is automatically created when the static website hosting feature is enabled.
For example, here's the command to deploy the folder's contents to a storage account named myblazorapp
. Make sure to run it in the dist folder.
$ az storage blob upload-batch --account-name myblazorapp -s . -d \$web
Note that depending on the shell being used, the dollar-sign in $web
may need to be escaped. Be sure to update tools (such as the Azure CLI and Storage Explorer) to the latest version, as earlier versions do not support the $web
container.
Temporary bug for macOS and Linux
There's currently a bug in Blazor 0.4.0 where the publish command doesn't properly copy the contents from wwwroot to dist. We'll notice that the dist folder doesn't contain the css and sample-data folders required to run the app. To work around this, we need to deploy the wwwroot and dist folders separately (and in this order).
Again, this workaround is only for Blazor 0.4.0 and earlier, and only for macOS and Linux.
$ az storage blob upload-batch --account-name myblazorapp -s out/wwwroot -d \$web
$ az storage blob upload-batch --account-name myblazorapp -s out/myblazorapp/dist -d \$web
This will be fixed in Blazor 0.5.0.
Run the app and set the correct content type
To see the app in Storage static website hosting, point your browser to the primary web endpoint of the Storage account that we noted earlier.
The app should be fully functional. However, if we look closely at the browser's console, we'll see an error.
The browser can perform streaming compilation of WebAssembly as the application is being downloaded, but it will only do this when the response has a content type of application/wasm
. This error is telling us that the WebAssembly file is served with a different content type and the browser is waiting until the entire file is loaded before compiling it.
When we upload files to Blob Storage using the Azure CLI, it automatically tries to guess the content type of each file. But it doesn't know about .wasm
files yet, so it will serve them as application/octet-stream
.
To fix this, we can modify the content type of mono.wasm in Storage using the Azure CLI.
$ az storage blob update --account-name myblazorapp -c \$web -n _framework/wasm/mono.wasm --content-type application/wasm
Now when we refresh the page, the error should disappear.
Enable deep linking
It is common for single page applications to use the browser's history API to create client-side routes that do not exist on the server. For instance, when we navigate to the "Fetch Data" page of the Blazor app, the URL changes to /fetchdata
. If a user refreshed this page or they saved the link and navigated to it at a later time, the browser will attempt to retrieve /fetchdata
from the server. Because there is no resource at that location, the server correctly returns an HTTP 404 error.
On web servers that support URL rewriting, the proper way to solve this problem is to rewrite the URL on the server so that /index.html is always served on requests to paths that might be a deep link in the SPA.
Azure Storage static website hosting currently does not support URL rewriting. However, it does allow an error page to be specified that will be returned when the server receives a request for a resource that doesn't exist. We can use this functionality to return the contents of /index.html for deep links.
Now when we navigate direction to /fetchdata
, the Blazor app will load and it will deep link to the "Fetch Data" page.
There is a catch, however: although the contents of /index.html is correctly returned and displayed, the HTTP status code is still 404. The user typically does not notice this and it should be an acceptable workaround in all browsers (the ones that support Blazor, anyway). Hopefully we'll have better support for this scenario in Storage in the future.
Learn more
To find out more about the new static websites feature in Storage and Blazor, check out these links:
In recent years, there's been a shift towards building applications with serverless architectures. Serverless backends are typically powered by fully managed and infinitely scalable services with consumption-based pricing, such as Azure Functions and Logic Apps.
Today, Azure Storage announced the public preview of its static website hosting feature that complements serverless backends by making it easy to also host frontend single page applications (SPAs) in a fully managed, highly scalable, consumption-based service. Blob Storage has always been able to serve static assets such as HTML, CSS, and JavaScript. The static websites feature adds more traditional web server capabilities that are required to host SPAs and statically generated websites, including support for defining default and error pages.
Blazor is an experimental framework from the ASP.NET team for building SPAs using .NET instead of JavaScript. It does this by running standard .NET DLLs in the browser on top of a Mono runtime that has been compiled to WebAssembly.
- Blog post announcing Azure Storage static websites
- Documentation on the static websites feature
- ASP.NET Blazor
In this post, we'll look at how to host a standalone Blazor application with no server-side code in a Azure Blob Storage static website.
Create a Storage account with static websites enabled
We'll start by creating a general purpose V2 Storage account from the portal.
After the Storage account is created, we can open it in the Azure portal. In the left navigation, we'll open the Static Websites (preview) feature, enable it, and specify index.html
as the index document name ("index.html" may already appear in the box as a placeholder, enter the value anyway).
When the settings are saved, the static websites feature is enabled and a primary endpoint is shown. This is the URL to our website.
Create a Blazor app
To create a Blazor application, we first have to install the Blazor project templates. If we're using Visual Studio 2017, we'll want to install the Visual Studio tooling for Blazor.
Instead of Visual Studio, today we'll be building our Blazor app with the .NET CLI. To install the Blazor templates, run this command:
$ dotnet new -i Microsoft.AspNetCore.Blazor.Templates
In an empty folder, we'll create a new standalone Blazor project.
$ dotnet new blazor
To build the static assets for the application, we'll use the dotnet publish
command:
$ dotnet publish -c Release -o out
This publishes the app to a folder named out.
Deploy the app to Blob Storage
Now we're ready to deploy the app. The assets for the app is in a folder named out/{project-name}/dist. To deploy it, we'll use the az storage blob upload-batch
Azure CLI command. The default static websites container is $web
; it is automatically created when the static website hosting feature is enabled.
For example, here's the command to deploy the folder's contents to a storage account named myblazorapp
. Make sure to run it in the dist folder.
$ az storage blob upload-batch --account-name myblazorapp -s . -d \$web
Note that depending on the shell being used, the dollar-sign in
$web
may need to be escaped. Be sure to update tools (such as the Azure CLI and Storage Explorer) to the latest version, as earlier versions do not support the$web
container.
Temporary bug for macOS and Linux
There's currently a bug in Blazor 0.4.0 where the publish command doesn't properly copy the contents from wwwroot to dist. We'll notice that the dist folder doesn't contain the css and sample-data folders required to run the app. To work around this, we need to deploy the wwwroot and dist folders separately (and in this order).
Again, this workaround is only for Blazor 0.4.0 and earlier, and only for macOS and Linux.
$ az storage blob upload-batch --account-name myblazorapp -s out/wwwroot -d \$web
$ az storage blob upload-batch --account-name myblazorapp -s out/myblazorapp/dist -d \$web
This will be fixed in Blazor 0.5.0.
Run the app and set the correct content type
To see the app in Storage static website hosting, point your browser to the primary web endpoint of the Storage account that we noted earlier.
The app should be fully functional. However, if we look closely at the browser's console, we'll see an error.
The browser can perform streaming compilation of WebAssembly as the application is being downloaded, but it will only do this when the response has a content type of application/wasm
. This error is telling us that the WebAssembly file is served with a different content type and the browser is waiting until the entire file is loaded before compiling it.
When we upload files to Blob Storage using the Azure CLI, it automatically tries to guess the content type of each file. But it doesn't know about .wasm
files yet, so it will serve them as application/octet-stream
.
To fix this, we can modify the content type of mono.wasm in Storage using the Azure CLI.
$ az storage blob update --account-name myblazorapp -c \$web -n _framework/wasm/mono.wasm --content-type application/wasm
Now when we refresh the page, the error should disappear.
Enable deep linking
It is common for single page applications to use the browser's history API to create client-side routes that do not exist on the server. For instance, when we navigate to the "Fetch Data" page of the Blazor app, the URL changes to /fetchdata
. If a user refreshed this page or they saved the link and navigated to it at a later time, the browser will attempt to retrieve /fetchdata
from the server. Because there is no resource at that location, the server correctly returns an HTTP 404 error.
On web servers that support URL rewriting, the proper way to solve this problem is to rewrite the URL on the server so that /index.html is always served on requests to paths that might be a deep link in the SPA.
Azure Storage static website hosting currently does not support URL rewriting. However, it does allow an error page to be specified that will be returned when the server receives a request for a resource that doesn't exist. We can use this functionality to return the contents of /index.html for deep links.
Now when we navigate direction to /fetchdata
, the Blazor app will load and it will deep link to the "Fetch Data" page.
There is a catch, however: although the contents of /index.html is correctly returned and displayed, the HTTP status code is still 404. The user typically does not notice this and it should be an acceptable workaround in all browsers (the ones that support Blazor, anyway). Hopefully we'll have better support for this scenario in Storage in the future.
Learn more
To find out more about the new static websites feature in Storage and Blazor, check out these links: