Blazor WebAssembly applications
.NET based Single-Page Applications for real ?
TL;DR; this post details our first steps with Blazor WebAssembly. Recently, our .NET development team had to implement a new feature for an end-to-end handling the full stack. Our B2B Product Management team required a rich client-side interactive UI, but the frontend developers we used to work with were not available. To preserve our productivity, we decided to explore the new ASP.NET Core Blazor WebAssembly framework. So, we tried it… and we love it! It still has to mature, but for our needs, it is fine.
I Love My Local Farmer is a fictional company inspired by customer interactions with AWS Solutions Architects and AWS Developer Advocates. Any stories told in this blog are not related to a specific customer. Similarities with any real companies, people, or situations are purely coincidental. Stories in this blog represent the views of the authors and are not endorsed by AWS.
Our .NET development team takes care of all the applications and systems that support the acquisition, conversion, registration and on-boarding of new farms on our marketplace. These are critical systems for our business, as the more new farms we embark, the more new consumers we can reach. Our B2B Product Management team is on a journey to streamline the registration and on-boarding process. Right now, it can take up to two weeks for a new farm to on-board. This is mainly due to the proof of address due diligence. We require that the farm owner provides proof of address. We use it to check the actual existence of the farm. Currently, they have to send us this document by mail, and a human operator has to review it. The B2B Product Management team wants to turn this process into a fully automated process, where the farmer can log in and upload the required documents. Then, the documents will be automatically processed. A human operator will have to review them only if there is a doubt. That’s it for the business context.
Blazor WebAssembly, a component based SPA framework
The reason we love .NET is that it is an all-purpose development platform. At a point in time, all good ideas will end up in .NET. The .NET solution for the rich client-side UI is Blazor. Blazor is a component based framework. Those who are familiar with Angular, React, Vue.js, or Svelte, will be at home on that part. Blazor leverages the Razor syntax. Razor is a markup syntax for embedding .NET based code into webpages.
Blazor comes with two hosting models:
- Blazor server: the component graph is held on the server side. SignalR is used to maintain a connection between the client and the server through WebSockets. User interactions are sent to the server. Blazor renders the component graph. A UI diff is calculated and sent back to the browser in a binary format.
- Blazor WebAssembly: it is a single-page application (SPA) framework for building rich client-side web apps with .NET. Blazor WebAssembly relies on the WebAssembly (wasm) binary format standard to execute .NET code in the browser. As WebAssembly is an open web standard supported by all major browsers, so are Blazor WebAssembly applications.
Blazor server has many advantages but it still requires network hops for every user interaction, and that’s exactly what we want to avoid. Handling all the user interactions on the client-side reduces the server load. It also enables to serve the application from a CDN like static content. The good news is that whatever the hosting model you choose, the component model is the same. You can switch from one to another without too much pain, if you use abstractions that allow your components to be agnostic to the hosting model.
So, we have decided to give a try to Blazor WebAssembly.
How does Blazor WebAssembly work?
Ok, so my .NET code is compiled to Wasm format, right? Well… it depends. Before the .NET 6 release, your code and dependencies wasn’t compiled to Wasm. Blazor relies on the open source cross-platform .NET runtime called Mono. Mono has been compiled to a Wasm module. Your code is still compiled to Intermediate Language (IL) code. The Wasm version of Mono interprets your IL assemblies or dependencies to execute them.
In .NET 6, the support for Ahead of Time (AOT) compilation has been added. It is not activated by default. You need to add the following lines to your
.csproj file to activate it:
AOT compilation occurs only when you publish your application to avoid slowing down your development workflow. AOT compilation adds indeed several minutes to your build on small project and potentially much more on large project.
The size of your binary will also be larger than if you compile your application to IL code.
Using AOT compilation is a trade off between runtime performance for CPU intensive application and load-time performance. If your application is not CPU intensive, you may not see any benefits from AOT compilation, or at least enough to pay for the extra size of the content to download.
How to serve a Blazor WebAssembly application?
A Blazor WebAssembly application is fully downloaded to the browser and executed on the browser UI thread. You have no dependency to a backend server to run your application. It is then a candidate to be served through a static website hosting service like Amazon S3 or a Content Delivery Network (CDN) like Amazon CloudFront. In this case, we call it a Blazor WebAssembly standalone application. To bootstrap a standalone Blazor WebAssembly project, you just have to run the following command:
dotnet new blazorwasm
As an alternative, you can create a project with an ASP.NET Core backend app to serve your Blazor WebAssembly static files. The downside of this approach is that you have an application running server-side with the associated cost. However, you get the ability to prerender your Blazor WebAssembly application before all the content is loaded into the browser. The server composes an HTML page to serve for the requested URL while the static assets are being downloaded. It may help improve the perceived load speed of your application by your end users. To boostrap a hosted Blazor WebAssembly project, you can execute the following command:
dotnet new blazorwasm -ho
To host your ASP.NET Core backend app, you can use a service like AWS AppRunner. You package your backend application as a container image, and AWS AppRunner, a fully managed service, will deploy it and run it for you.
What to know about Blazor WebAssembly app consuming AWS services?
If you’ve read some of our previous posts, you know we are an all-in-AWS company. So, we can’t validate a technology without taking a look at its compatibility with the AWS services. You can find some getting started content to help you with your first steps with Blazor WebAssembly on AWS like this blog post and this one about Blazor WebAssembly and Amplify. But they are missing a really essential part: the limitations you will likely encounter. Let me light up your lantern here.
Blazor WebAssembly authentication and authorization with Amazon Cognito
Blazor WebAssembly default authentication and authorization mechanism is built on top of OpenID Connect (OIDC) protocol. Nowadays, it is kind of a standard choice for SPAs.
If your application requires you to grant permissions to your end users to interact with some of your AWS services, using Amazon Cognito is the most straightforward option. Amazon Cognito is a fully managed identity service that manages user identities, federation with external SAML and OIDC identity providers and user permissions to access AWS services. Unfortunately, while Amazon Cognito implements parts of the OIDC protocol, it is not a fully OIDC-compliant provider.
Thus, if you try to use Amazon Cognito as your OIDC provider for your Blazor WebAssembly application, you will experience time-outs when signing-in, because Cognito doesn’t support the
prompt=none parameter. You may want to build your custom version of the AuthenticationService.js file as mentioned here, but I don’t recommend this workaround. You will find yourself maintaining your own fork of this library which is highly sensitive since it is related to identity.
AWS service APIs call from Blazor WebAssembly, why it is not supported
Another limitation comes from the missing support of the System.Security.Cryptography APIs on Blazor WebAssembly since .NET 5. To make an API call to AWS services, you need to sign your request with the AWS Sigv4 algorithm. The AWS SDK for .NET relies on the System.Security.Cryptography APIs. So, you can’t use the AWS SDK for .NET to make API call to AWS services.
Backend For Frontend pattern is your Best Friend Forever
OK, so what does it mean? Is the game over? No, because you can call the Backend For Frontend pattern to the rescue. This pattern is becoming more and more popular in the SPA application development space to apply a strict separation of concerns between your frontend and your backend. But it can help secure your application too, by keeping interaction between your application and your identity provider on the server side.
In our case, we use it for encapsulating the interactions with our Amazon Cognito identity service and the calls to the AWS service APIs. Our backend for frontend only knows our BFF APIs, and we secure the communication between our Blazor WebAssembly frontend application and our BFF API application through a cookie authentication. To do so, on the Blazor WebAssembly side, we have implemented our own AuthenticationStateProvider. I will dive deeper on this part in a future post. One may think we finally returned to having back and forth between a frontend and a backend. Keep in mind that here we are just building a façade to AWS services. If you don’t need to call them, you don’t have any remote call.
When we first gave a look at it, Blazor WebAssembly seemed a viable option to us. So, we have built a proof of concept to get our hands dirty. If you know the Razor syntax, it is pretty straightforward to start with Blazor WebAssembly. And if you have already worked with ASP.NET MVC or Razor Pages, like us, you know Razor.
Hosting our Blazor WebAssembly application on Amazon S3 and serving it with Amazon CloudFront was easy. We’ve just had to follow tutorials about hosting static website with Amazon S3 and Amazon CloudFront. For hosting your BFF API, you can select any service that is able to host a .NET web API. In our case, we have chosen AWS App Runner.
The hardest part was about dealing with authentication with Amazon Cognito. You can watch the following YouTube video to get more info on it. In my next post, I will go into the details of the proof of concept we’ve built.
Others are considering using Blazor WebAssembly in production. In fact, some are already in production for a while like Steve Peirce for their Powered4.tv video-on-demand site for independent wresting content. You can read his post on their learnings.
Blazor WebAssembly definitely upscales our team capability. We are now able to develop rich client-side application own our own. We have decided to continue to build our application with this new super power.