A Weekend with Blazor: Running C# in the Browser

WebAssembly lets .NET developers conquer the web browser. But is it ready for prime time?

Matthew MacDonald
Nov 22 · 14 min read

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.


What if you could write code that runs natively inside the browser, but isn’t JavaScript? It would live in the same sandbox and have the same universal compatibility. And it wouldn’t need a clunky plugin.

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.

You can learn more about WebAssembly here. But the important takeaway is this: before WebAssembly, it would have been technically possible to build something like Blazor. But the performance would have lagged so far behind JavaScript, it would have made a 1990s dial-up connection look good.

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 runs your code on a web server, using the familiar .NET environment. The trick is how the browser and the server communicate. As the user interacts with the page, JavaScript code calls back to the server where the real page lifecycle happens. (To make this connection, the page uses a Microsoft API called SignalR.) After your server-side code runs, Blazor Server renders the page and sends the changes back to the web page, which updates itself accordingly.
  • Blazor WebAssembly runs your code in the browser, using a miniature .NET runtime that’s powered by WebAssembly. Your client-side code has access to many of the familiar .NET libraries, and you write it in the genuine C# language. Yes, you can still call an API on the web server, just as you would in a JavaScript page. But the heart of the application (and all its user interface) is handled in the browser.

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.

You might think that the Blazor architecture seems a little bit like putting a virtual machine (for .NET) inside another virtual machine (for JavaScript). You wouldn’t be completely wrong. The difference is that WebAssembly allows the .NET runtime to avoid the overhead of JavaScript — specifically, its cycles of parsing, compiling, and optimizing.

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:

  1. Install the .NET Core 3.0 SDK.
  2. Install the latest preview of the .NET Core 3.1 SDK. (Blazor will be released with .NET Core 3.1.)
  3. 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:

This sample web page uses the arguably old-fashioned Bootstrap library of styles, mostly to ensure that you can write a reasonably attractive Blazor demo without any extra stylesheet fiddling. Interestingly, index.html doesn’t use any JavaScript libraries, except for one. That’s blazor.webassembly.js, the script that downloads the runtime and kicks off the whole process.

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:

The @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 @page directive.

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:

@layout MasterLayout

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.

Building the EightBall app wasn’t quite as easy as I expected. First, the workflow of Blazor code doesn’t resemble vanilla JavaScript because your code can’t directly access the HTML DOM. (This is a current limitation of WebAssembly.) So it doesn’t make sense to casually grab elements and change their properties. You could do that with a layer of JavaScript interop, but it gets ugly fast. You need to add an extra layer of JavaScript functions to call, which is distinctly crufty.

Instead, Blazor has its own mechanisms that you can work through to update the page. Rather than using a control model, Blazor favors a system of data binding, like Razor and many modern JavaScript frameworks. That means that the easiest way to get a value into a control in your web page is to compute it in code, set a variable, and bind that variable in your page.

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:

@code {
void AskEightBall()
{
}
}

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 answer inside:

<p>@answer</p>

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.

I wish I could say this approach was painless, but I ran into some gaps trying to apply my JavaScript skills in the Blazor world. The changing image visibility didn’t always work unless I called the page’s HasStateChanged() method (and even then it was a bit quirky). The asynchronous model of C# didn’t line up with the expectations I had from JavaScript. I appreciated being grounded in real HTML and CSS (rather than some sort of alternate UI system, like XAML in Silverlight), but I was frustrated by the indirection needed to access properties through binding.

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!

But not everything was so rosy. I also found a whack-load of JavaScript interop code. Key features, like playing sounds, just weren’t accessible natively in Blazor. At the end of my exploration, I was forced to conclude that even C# purists would’ve found it easier to write that application in pure JavaScript.

Here was the first real Blazor shortcoming — an extra layer of insulation that makes JavaScript-skilled developers less efficient. In time, Blazor will probably offer its own classes that wrap key JavaScript features like local storage, geolocation, and history management. But right now, these key ingredients need JavaScript interop, which means extra work and extra complexity.

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.

Benchmarking 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.

Blazor is still an early-stage technology, with plenty of optimizations left to be made. Last year, some benchmarks suggested its performance lagged JavaScript by a factor of 100. More recently, the same set of benchmarks suggested that Blazor is roughly ten times slower than classic JavaScript:

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.

The fact that Microsoft is the owner of Blazor is no small drawback. Yes, Blazor is a modern, open source framework. But it’s also owned by a company with a long history of abandoning yesterday’s shiny new technology for something else. For that reason, most developers will approach Blazor with well deserved caution. As long as JavaScript can do everything Blazor can do, without the extra challenges of download size, performance, and a new tool stack, most developers will stay put.

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.

Young Coder

Hack. Code. Think. Stories about science, tech, and programming.

Matthew MacDonald

Written by

Teacher, coder, and author of heavy books. Join me at Young Coder for a creative take on science and technology. https://medium.com/young-coder

Young Coder

Hack. Code. Think. Stories about science, tech, and programming.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade