The path to .NET 5 and Blazor WebAssembly with some fun sprinkled in
At Ignite 2019, we unveiled Rock, Paper, Scissors, Lizard, Spock (RPSLS), a web game based on the game created by Sam Kass and Karen Bryla that ups the difficulty of the very well-known game Rock, Paper, Scissors. When we built RPSLS, the goal was to show that services built with any language can run in Azure at scale. When launched at Ignite, over 2,000 unique people played RPSLS in the first week, most multiple times. You can find the code of RPSLS on GitHub.
So, what is RPSLS exactly?
RPSLS is a microservice architecture solution built inside of Azure Kubernetes Service. In the game, you can play against a friend using a unique URL, or you can play against a bot. What is a bot? A bot is a “mascot” representation of 5 of the many languages you can build apps with inside Azure. The ones we picked were: C#, Java, NodeJs, Python and PHP. When you play against a bot, you are actually playing against a microservice running in that particular language. For instance, when you choose C# (my favorite language) as an opponent, there is a service running in .NET that predicts my next move. Once the service “guesses my move”, it attempts to pick a move to beat me. That prediction runs in .NET. Along with the .NET bot microservice running in .NET, the web UI also runs in .NET, .NET Core 3.1 Blazor Server to be exact.
Updating the App to Use the Latest Bits
With the recent release of the unified .NET 5 with Blazor, it made sense for RPSLS to be upgraded as well. This isn’t extremely challenging given the hard work the .NET team put into providing a clear upgrade path. Knowing upgrading the bot service would be easy, we turned to the front-end and thought it would be interesting to not just upgrade to .NET 5, but also port the app from Blazor Server to Blazor WebAssembly. Going through this upgrade was extremely interesting, as it was fairly easy. In essence, here are the things that we accomplished as part of the migration:
- A new project was created using the Blazor WebAssembly hosted template. This creates 3 projects; a
Clientproject for the UI, a backend
Serverproject for the UI to call, and a
Sharedproject which will hold shared models and services.
- Move the file content from the Blazor Server
_Host.cshtmlpage content to the
index.htmlin the static folder of the newly created project and replace the existing
_framework/blazor.server.jswith the new
_framework/blazor.client.jsreference in that file. The newly referenced .js file will be the new entry point of the SPA.
- Migrate all .razor files in the old project to the new Client project
- Using the Options pattern in .NET 5 in the Client project to retrieve any configuration settings from the Server project using modern retrieval methods (browser fetch API)
- Configure the Client project to use JSRuntime to initialize Google Analytics with the Api key fetched from the backend (the Blazor Analytics NuGet package allowed us to do it out of the box)
- Implement a custom AuthenticationState provider that checks if the user is logged in before calling the server (the user can either log with Twitter, or provide a string that represents their username
- gRPC calls made from Blazor components to the Game API are now executed from Client project and, as the game API is not exposed to internet, we created a game a factory service to forward the requests to the backend Server API.
This is an oversimplification of the steps needed to complete the migration, but it was not very complicated. At the same time, it was not trivial, but as mentioned, the hard work of the .NET team to make these upgrades easier was on display. For reference, the code changes for the WebAssembly migration is available as a PR on GitHub.
Overall, the experience was great! Developing multiple layers of the app all in the same language made it feel like you aren’t building a client side app, but since the Client project gets compiled down to native WebAssembly, you are. Building a true client-side application, and still get support for Dependency Injection, the Options Pattern and Http/gRPC clients in the same way as the backend makes the migration very straightforward. One of the key points is that our application doesn’t require persistence, so we were able to migrate to Blazor WebAssembly without big changes in the components. We want to thank the .NET team for making .NET 5 and Blazor more accessible for existing applications on previous versions of the platform when it comes to migrating. We encourage everyone to check out the GitHub repo and see what we did to get to the latest version of the app, and of course play the game, for no other reason other than it is fun.