A Weekend with Blazor: Running C# in the Browser
WebAssembly lets .NET developers conquer the web browser. But is it ready for prime time?
There are plenty of WebAssembly-powered projects under way. But one of the fastest out of the gate is Blazor, a .NET stack for building single-page web apps that recently graduated from speculative preview technology to early-stage beta. Microsoft plans to release the first version of Blazor in May 2020.
Blazor is long on vision, but its practical qualities take more time to evaluate. I decided to play around with the rapidly evolving technology. Here’s what I found.
That’s the promise of Blazor, a Microsoft technology that puts both C# and .NET in the browser. It works its sorcery using WebAssembly, a high-performance pipeline that precompiles code to a compact, binary format. Best of all, WebAssembly is supported by every major browser, including mobile versions.
The two types of Blazor
Before you dive in any deeper, let’s clear something up. There are actually two flavors of Blazor. As usual, the Microsoft branding people are hard at work doing what they do best — using and reusing new product names, and causing widespread confusion in the process.
The two flavors of Blazor share a similar programming model, but their technical underpinnings are completely different:
Blazor Server is a technology with some interesting use cases, but you’re obviously going to sacrifice some performance due to the constant communication — and don’t even ask about offline capabilities. Blazor WebAssembly is the one that’s received most of the hype — and the one we’re exploring in this article.
From now on, when I write “Blazor” take it to mean “Blazor WebAssembly.”
The architecture of Blazor
The most common misconception programmers have about Blazor is that their C# code is compiled into WebAssembly, then sent to the browser, and then executed. This approach isn’t out of the question — and the creators of Blazor have hinted that they may try this technique in the future. But it isn’t the way Blazor works today.
Right now, the only part of Blazor that uses WebAssembly is the runtime. All of the other ingredients in your application are compiled to intermediate language (IL), the bytecode standard for .NET since 2000. In other words, a Blazor application is compiled in exactly the same way as any other .NET application before it’s distributed.
So how does it all come together? When you visit a web page that uses Blazor, the page begins by downloading a scaled-down .NET runtime. Then it downloads your application and any other .NET library your application uses, all in their native IL. Finally, the Blazor runtime executes the IL.
There’s no requirement for you to write server side code in ASP.NET — or for you to write any server-side code at all. All you need to do is compile your Blazor app and put the files on a web server.
In theory, the performance of interpreted C# code in the browser should be good— after all, WebAssembly has already been used to port emulators and 3D games to the browser. However, we can expect some sort of startup tax when the application begins, because it needs to download the Blazor runtime. We’ll look more closely at Blazor’s performance after we build a simple page.
Setting up a Blazor environment
Disclaimer: If you haven’t already realized, Blazor is a pre-release, early-stage beta product. Key bits of infrastructure are changing, and you won’t get the same level of tooling support that you get with other types of Microsoft projects. That said, I tried creating a Blazor app in Visual Studio Community 2019 (the free version) and VS Code. Blazor worked in both environments with only minor hiccups.
Overall, I preferred the full Visual Studio 2019 experience for its conveniences — a single click of the Run button is enough to compile the application, start IIS Express, and launch the corresponding page in the browser. The IntelliSense is also less broken. (I’ve heard rumors of a Blazor extension for VS Code, but I didn’t want to go too far down the configuration rabbit hole with prerelease software.)
Microsoft offers full setup instructions for both versions of Visual Studio. Here’s what worked for me:
- Install the .NET Core 3.0 SDK.
- Install the latest preview of the .NET Core 3.1 SDK. (Blazor will be released with .NET Core 3.1.)
- Install Visual Studio Community 2019 with the “ASP.NET and web development” workload. Note that you don’t need the preview version Visual Studio 16.4) to run Blazor, although that should also work.
Once you’ve finished your setup, you can create a Blazor project painlessly. Just fire up Visual Studio, create a new project, and choose the “Blazor App” project. Visual Studio will ask you if you want a Blazor Server App or a Blazor WebAssembly App — and as you already know, it’s the latter.
Once your new app is generated, you can run it straight away. You’ll get the stripped-down demo project shown here:
Before you can make anything more useful, you need to peek behind the scenes and see how this example is constructed.
Your first look at a Blazor project
There’s a lot of boilerplate in a new Blazor project. If you haven’t programmed with ASP.NET Razor, you may feel a bit lost. (The name “Blazor” comes from taking the ASP.NET server-side “Razor” model and adding “B” for browser.)
Here’s an expanded view of the Solution Explorer on a fresh new Blazor app:
Thankfully, it’s pretty easy to sort out the clutter. The next sections take you on an abbreviated tour.
Starting at the root: wwwroot
The wwwroot folder holds the traditional web server files — HTML pages, CSS style sheets, images, and other resources. The index.html is the entry point for your application. Nothing happens until someone launches a web browser and navigates to this page.
If you peek inside index.html, here’s what you’ll see:
You should also notice the
<app></app> section. This holds the initial content — the stuff Blazor shows when the page first loads, before it’s had a chance to download the runtime. There’s a noticeable delay here, so it makes sense to replace the “Loading” message with some sort of animated GIF or app-specific information.
By design, every Blazor app has just one HTML file associated with it. That’s because Blazor apps are best designed as single-page web apps. They have the illusion of loading different “page-like” views, but the browser doesn’t perform any real navigation. (If it did, you’d need to reinitialize the Blazor runtime all over again on each new page, which would be a performance nightmare.)
So wwwroot shows you how your page launches Blazor, but where does Blazor hand off to your application code? The boilerplate is a tiny bit of autogenerated code in the Program.cs and Startup.cs files. You can take a look at these files, but there’s no need to touch their code. The real starting point is Blazor’s Index.razor page.
Pages and layouts
Blazor uses its own routing system based on virtual pages. Usually, the first page is Index.razor, but you’re free to create others as you require them. The starter Blazor project includes two more pages — Counter.razor and FetchData.razor — that are just examples. There’s nothing special about these names, and there’s no need to keep these files.
So what goes on in a Razor page? Here’s a quick look at Index.razor:
@page "/" directive defines the path that this page handles. In other words, Index.razor is the root page — if you don’t ask for a specific .razor page, you end up here. Click on one of the navigation links in the sample application, and you’ll go to another Blazor page with a different
The rest of Index.razor is ordinary HTML, with a couple of oddities. First of all the
<SurveyPrompt> element is a placeholder. It tells Blazor to insert the content of the SurveyPrompt.razor file, which is a basic block of HTML with the survey link. (Separating the survey content in a separate file makes it possible to reuse it on another page.) You’ll also notice what isn’t in the Index.razor page — namely, the sidebar with the navigation menu and the grey header with the About link. These details are defined in another template, called MainLayout.razor:
Every page has the opportunity to use a layout. The Index.razor uses MainLayout.razor because that’s the default that’s set in the App.razor file. However, you could also pick something different by adding the
@layout attribute at the top of a page, like this:
The MainLayout.razor page has the
<div> elements that carve the page up into two columns. It also has a reference to
<NavMenu> which, unsurprisingly, grabs the menu markup from the NavMenu.razor file. Finally, the MainLayout.razor file also has a special
@Body placeholder. This is where it puts the rest of the content for the page. So if your application starts at Index.razor, Blazor grabs the main layout, injects the menu in the first column, and then injects the Index.razor content in the second column.
Once you’ve sorted through these connections, you’re left with a highly componentized, modular system. You haven’t dug through all the details, but you’ve seen enough to get to the truly interesting part — code!
The EightBall App
I decided to give Blazor a whirl with a very simple example. I chose a demonstration of the infamous “magic eight ball.” Not only is this application trivially easy to build, it’s also the first example I made on another framework when it was new many, many years ago — WPF.
Here’s a simple example. If you have and you want to handle its click with Blazor code, you add a special
@onclick attribute. (Blazor uses the
@ symbol as a marker when it intervenes in ordinary HTML.) Here’s an example:
<button @onclick="AskEightBall">Ask the Eight Ball</button>
Now you need a C# function named
AskEightBall(). You put that in a
@code block, and you put the code block at the end of your .razor file, after the markup:
But just triggering the code isn’t enough. You also need somewhere to display the result. Here we add a paragraph to hold the answer, and put a variable named
Now the AskEightBall() function has a simple job. Set the answer variable, so it appears in the page. Blazor notices it automatically, so there’s no need to refresh anything.
Here’s the complete Index.razor page:
I made some more changes after this. I had to bind the
class attribute of the
<img> so I could switch it between two classes, making it visible during the “thinking” process and then disappear after. I also added the C#
async keyword and
Task class to let this process unfold without locking up the browser.
The interop problem
I decided to compare my experience to a much more sophisticated example. I picked a Blazor-powered Asteroids clone (you can download the code yourself, or play it online). Here I found an application that performed smoothly and managed a neat trick: it put its logic in a class library and reused it in several different types of .NET applications, including a classic Windows Forms interface. Nice!
The download tax
Blazor is a new technology, and developers working with Blazor are in for some growing pains. But Blazor also has another drawback that has nothing to do with its programming model — its size. And this problem isn’t a tiny weak spot, but a large and soft underbelly.
To survey the damage, I ran my EightBall app and took a look at the network activity in Chrome’s developer tools. Here’s a snapshot, with the cache disabled, and sorted by file size:
At the top of the list, clocking in at just less than a megabyte, is mono.wasm, the Blazor runtime that makes everything work. The next largest ingredient is mscorlib.dll, which holds some of the fundamental .NET class libraries. After that is the animated GIF the app uses, followed by a whole pile of much smaller .NET DLLs. Scroll down the list and you’ll eventually find the DLL for the EightBall application itself, EightBallApp.dll, which is a mere 10 KB big. The total size of everything downloaded in this first request that starts the application and grabs all the code is a hefty 2.6 MB.
In some contexts, 2.6 MB would hardly warrant a second thought. Many websites use large images, animated GIFs, or videos that blow past this mark. But Blazor’s large size is more limiting than that, because nothing can happen until the runtime and your code is downloaded in its entirety. Until then, you’re watching the “Loading” message (or whatever other content you’ve put in the
<app></app> section of index.html). Typically, that means you’re in for a startup delay of 2–4 seconds.
Can Blazor be pruned to a smaller size? It’s unlikely that there’s much more optimization to squeeze out of the Blazor runtime itself. However, there’s plenty of talk about tree-shaking algorithms that could shrink other .NET DLLs by removing parts of the code that aren’t being used in your application. Caching can also alleviate part of the problem by letting you download the runtime once and then reuse it across other requests and sites. But until the size situation improves, there’s a giant asterisk attached to Blazor.
Due to the size of the runtime, Blazor’s first-request performance is poor. But once things get started, how does it do?
Before getting to deep into the answer, it’s important to note that performance is a difficult thing to assess for any application, and it’s harder still for a browser-based app. Should a benchmark look at the speed of running certain algorithms? How long it takes to update the user interface? If you’re not building games, it’s quite likely that neither of these details will be the ones limiting performance. You’re far more likely to be at the mercy of issues beyond your control — like waiting for a response from a slow server API.
This isn’t as concerning as you might think, because there are dials left to twist. Over time, the Blazor runtime will be more thoroughly optimized. At the same time, the browser’s implementation of WebAssembly will also improve. For that reason, these lackluster benchmarks aren’t a huge concern. They’re a floor, not a ceiling.
Final thoughts: The good, the bad, and the Microsoft
It’s not difficult to understand the excitement behind Blazor. What’s murkier is the all-important question: Will it last?
If you’ve been coding for more than a few years, you’ve seen the death of many other hotly hyped technologies. Consider Microsoft Silverlight, which was a lightweight plugin-powered version of .NET than ran on any platform. It was the rich client web-modernizing technology of the future, until it wasn’t.
That doesn’t mean Blazor can’t gain ground in all of these areas. It could even become a dominant force in the development of .NET web applications. But if I had to make a wager today, here’s what I’d bank on. WebAssembly is the future. But for now, Blazor is just an interesting possibility.
For a once-a-month email with our best tech stories, subscribe to the Young Coder newsletter.