Intro
For as long as browsers have existed, there has been one and only one programming language that has dominated developing the web: JavaScript. And as long as there has been JavaScript, there's been bemoaning and complaints about the inadequacies and inconsistencies of the language. Some of it is warranted. Some of it is not. Regardless, many an individual, companies and the like have attempted to replace JavaScript with something else. Something better.
Blazor is Microsofts latest (more on that in a moment) attempt to leverage their existing Dotnet ecosystem to do just that: make JavaScript obsolete and write web applications and websites with C# and Dotnet.
Brief History Of JavaScript Replacements
Replacement attempts to JavaScript are not a new concept. It has been attempted probably almost as soon as the language gained prominence in the mid to late 90s. The late 90s and early 00s saw the rise and fall of Java applets: tiny applications written in Java that could be embedded into a website as a standalone component. The early 2000s introduced the world to Flash and due to the multimedia capabilities (none of which was present at the time in html, css or JavaScript) it became a very popular way of displaying animated and lively content. Actionscript, a close cousin to JavaScript, allowed even further power to developers. Most people probably remember games and video players from that era that popped up pretty much everywhere. Flash sizzled out as a technology in less than a decade mostly, I argue, due to html 5 and the release of Google Chrome and it's V8 engine.
In the same time frame Flash was getting notoriety, Microsoft released its first attempt at a solution in the form of Silverlight. Silverlight was essentially the same concept Blazor leverages today: Write your code in C# and xaml without any need to write JavaScript. However, due to poor adoption by browsers and emergence of html 5 put a cap in that technology quickly.
Microsoft's second attempt came through ActiveX but unlike Silverlight, ActiveX allowed a broader range of programming languages. Security issues and decline in Internet Explorer (the only browser truly supporting the technology) popularity proved the downfall of this attempt as well.
Others too, tried and failed. Some attempted to replace JavaScript entirely with different languages. Others opted for a halfway solution via extensions to JavaScript itself like CoffeeScript and more recently TypeScript. JavaScript development itself transformed as well to a point that few these days write it raw, opting to use frameworks such as React or Angular. Then came Blazor.
About Blazor
Somewhere in 2010s, the existing .NET was deemed unsuitable for future development. Or so I assume. Regardless, a new framework arose alongside it called .NET Core. By the time version 3.1 had come out, a decision was made to merge the two into a single unified .NET, which later would be released as .NET 5, skipping version 4 entirely (Microsoft likes to skip numbers).
During the Core era in 2017, Blazor arose as an experiment in Oslo by Steve Sanderson. It didn't take long for Blazor to receive an official slot in the .NET ecosystem and by the time .NET 5 came out it was all but an official piece of the .NET pie. Ironically, that was also the era when Blazor stopped working on Internet Explorer and older versions of Microsoft Edge, which, looking back at the history of these attempts, is almost a scandalous prospect to contemplate.
Blazor leverages a new browser technology known as WebAssembly that, in short, attempts to allow a new interface to develop software in the web that runs in the browser. It is not a drop-in replacement for JavaScript but seems like it was designed to allow offloading some heavier calculations to a faster engine and code than what the browser engine currently allows. JavaScript is fast. Blazing fast since the inception of the Chrome V8 engine (and further developments since) then but the age of the engines and the language is starting to show. Yet, the web being the universal interface and backward compatibility is a hard requirement, rewriting everything from scratch and deprecating decades of history would be a suicide as far as technology adoption is concerned. Hence, WebAssembly.
What blazor does, from simply the developer's perspective, is it allows you to write your code in Razor and C# and the code is then compiled into WebAssembly. You are, in essence, running an entire .NET framework in your browser. An interop JavaScript exists to translate .NET code into JavaScript and vice versa. WebAssembly does not have access to DOM, the primary means with which to affect the web page and thus, this "glue" is required to make anything possible.
Blazor comes in two flavors: server side (known as blazor server or simply blazor) and client side (blazorwasm). The difference between the two is as follows.
Blazor server runs on the server (duh). Each interaction with the page from the user is sent to the server. That means your clicks, selects, text inputs etc. need a roundtrip to the server before being processed and response displayed on the page. Websocket, which is a browser's real-time two-way communication platform, serves as the go-between of these interactions so the experience is still app like.
Blazorwasm on the other hand, acts more like Angular or React that the code managing the web page all live in the browser.
And with this difference, we come to my first annoyance with the technology.
The Subtle Differences Between Wasm And Server Can Bite You In The Ass
Blazor comes off as a singular unified concept and it actually pulls it off as far as developer experience goes. Mostly. There are however differences that will infuriate you if you don't know it beforehand. Here's what I stumbled on.
My experience with Blazor began through wasm. I felt like running every click and type to the server would be an unacceptable delay and dependent way too much on an active and robust internet connection. However, as I began to write an app that was meant to run in the local machine alone (meaning served locally on the machine as well) I felt Server was worth looking into since any network delay would be negligible. Imagine then my shock when I leveraged my wasm knowledge and when it was time to click, nothing happened. No events were fired. No interactivity of any kind. The page showed content alright but when it was time to submit forms or handle click events, nada.
My naivete in the uniformity of Blazor became my small undoing. In retrospect, I suppose I should have treated Blazor Server and Blazor Wasm something like AngularJS and Angular but hey they seem to work the same in every other respect.
You see, as it turns out, Blazor Server has a unique trait called Render Mode. What is Render Mode? Well, as I see it, it instructs Blazor to optimize itself. If you only present information and have no interactivity, you really don't need all that boilerplate and plumbing to handle all those clicks that never materialize. Hence, Blazor acts like a static website instead. Plain content. Render Mode needs to be in InteractiveServer
mode for that to work.
And it is not ON by default.
I know. I know. RTFM, right? Cut me some slack, please. If everything else works almost as universally between the two variants, is it reasonable to even assume this kind of little tidbit to be necessary for one to work and not the other? I say no.
It confuses me as to the justification for a setting like Render Mode and why is it how it is by default. One would imagine the primary purpose of Blazor to be the desire to create a single-page application experience and real time app. Why would you then disable by default all the code necessary to make that interactivity happen? I do not understand. After all, if you want to create just a static page, create a bloody static site. We have the technology. Personally, I'd have gone the opposite route: enable interactivity by default and for those who wanted to finetune case-by-case basis, allow this mode to be tweaked.
And by the way, there is no indication of any kind that this mode is the culprit. No log messages or error messages or warnings that interactive mode is X. I went down the rabbit hole trying to unravel why my components were disposed immediately after init thinking there was a bug in the framework itself before realizing this snafu.
Anyway, now I know you need this mode. And now, so do you. I hope this saves you a couple of hours of frustration, debugging and grey hairs. The good thing is, you only need to do it once in App.razor
and the effect trickle's down like a booming capitalist economy. Unless you really want to do that fine-tuning per page or something.
This is what it takes by default to make all your pages and components interactive:
<body>
<Routes @rendermode="InteractiveServer" />
<script src="_framework/blazor.web.js"></script>
</body>
Let's look at another feature next that on paper, seems like a wonderful thing, but in practice, could use some more polish.
CSS Isolation Works... Until It Doesn't
Blazor has this feature that is pretty ingenious as far as css isolation is concerned. Here's how it works.
If you create a component in Blazor and then create an accompanying css file, the build process does something interesting. It adds to the html an attribute and a matching css selector so any css you write in the component css file is scoped to that component alone. Like so:
So here's what the razor file would look like. Just a basic component. Then let's look at the css.
Notice the lack of specific selectors. Just button. And the result would, as expected, look like this:
One might assume based on that code that every button would be red, right? Not so. Here's where Blazor's magic comes in. Let's look at the generated html.
Notice the attributes? The build process has added these and made them unique throughout the html tree depending on the component (or file) the code is in.
Main
, div
and article
elements are different from h1
, p
and button
because the first three are in MainLayout.razor file while the latter three are in Counter.razor. Looking at the generated css:
We discover the same attribute as a selector making it as precise (or more so) as an id attribute to ensure only the specific component buttons are red.
The ability to use attributes as a css selectors have been in the spec and supported by browsers for a while but Blazor might be the first time I have seen them used like this. Usually attribute selectors have been used to target specific input fields via name or type like so input[type="text"]
but the spec seems to allow for just the attribute as a selector as well making it a cool way to create self-contained, isolated components without the need to add classes or ids to every piece of component built.
However, not all is sunshine and roses with this feature. There are, unfortunately, exceptions. And the most glaring of those is EditForm
.
If you were to naively write code like this:
<EditForm Model="FormData">
<InputText @bind-Value="FormData.Name" />
</EditForm>
form {
background: red;
}
input {
background: blue;
}
You would be surprised to find that the form is not red and the input field is not blue. What's with that? For some reason or other, EditForm
does not respect the same isolation rules. In fact, EditForm
does not have isolation rules it seems. The generated html does not have custom attributes inside the generated <form>
, while the css still does have the selector.
In order for css isolation to function within such an environment, you need some extra magic for both the razor code and css.
<div>
<EditForm Model="FormData">
<InputText @bind-Value="FormData.Name" />
</EditForm>
</div>
::deep form {
background: red;
}
::deep input {
background: blue;
}
Let's break it down. Since <EditForm>
won't receive the isolation attribute, we need something that does. Hence the div
. But that is not enough because the css previously still targeted the actual form and input fields. So we use ::deep
, which switches the selector scope so the attribute selector is a parent selector like so:
[b-jarguqllqi] form {
background: red;
}
[b-jarguqllqi] input {
background: blue;
}
Personally, I dislike this approach because it adds an unnecessary extra layer to the generated html but that seems the only way to make isolation work.
Next one is not a big deal but it is one to keep in mind when developing Blazor applications.
Deviating From Expected File Structure Has Consequences
The basic structure of a Blazor app is that you've got a single folder Components
within which you have all the code. However, some developers might want to organize their files in a different method by grouping files together by theme rather than by type. This is known as a feature model or vertical slice. So instead of having separate folder for Models, Controllers, Views etc. you have FeatureA and FeatureB and within each feature folder you have the associated model files, controller files, views, components, services etc.
Should you attempt to use this method in Blazor, expect to do some extra work. Mainly, you might need to import extra libraries into the feature folder itself. For example, to do interaction with buttons and the like, you need to import @using Microsoft.AspNetCore.Components.Web
. Despite the fact that there is already an _Imports.razor
file, you still need to do this if you deviate from the Blazor's default structure. There are two ways to import these libraries into your component.
- Import directly into the component
- Create a
_Imports.razor
file at your feature folder
Finally, let's discuss very briefly some error management.
Stacktraces Are Useless
I have observed that stack traces on the frontend seem to be mostly useless. Sure they still work on the backend but if you try to parse any stack trace you see on the browser console, you are fucked. The abstraction layers are so thick and deep that it is impossible to trace where in your code the problem is. Instead, you are relegated to the oldest of old-school debugging: removing code until things work and then adding it, veeeery carefully, back in again. It doesn't help that hot-reload logic is inconsistent and unreliable at times depending on the kind of code you write so there might be stacktraces for reasons outside of your control that have nothing to do with the feature you are writing.
Conclusion
Blazor on paper seems like a wonderful technology especially if your bread and butter comes mainly from .NET ecosystem. Being able to code everything in a single language is a dream many a developer have tried to implement. Context switching is a real pain and breaks often the flow of a developer's mind. NodeJS began as a dream like this and may be currently the most successful attempt to use a single language to handle the full stack of software development journey from UI to server to database (see MEAN stack for more on that) and that language is JavaScript.
Blazor comes close. Really close. But would I recommend this for anything but a hobbyist project and perhaps some internal company pet project? No. If I needed to write mission-critical software tomorrow from scratch, I would still opt for the UI to be React, Angular or any other of that ilk. I would use .NET in the backend though. 100%. The overall inconsistency in the developer experience for Blazor is just too much. Yes, now that I know these pain points, I can compensate for them but would I suffer another developer to face the same frustrations and pains in order to deliver value? I would not.
As an aside the talent pool in the wild still heavily favors JavaScript and its various frameworks as well, making Blazor a hard sell as an investment. Granted, it is a catch-22. There won't be experienced developers for the technology if no one uses the technology but for me to seriously begin to contemplate recommending Blazor to my customers or colleagues, I need to wait a little while more. See if some of these pain points of mine are smoothed over and rectified.
Time will tell if Blazor becomes another Silverlight or a viable alternative to plain old html/css/js stack. It is entirely possible, that another language might take a bite at this apple as well. Some already have. Rust for example can be compiled to webassembly though I know not if it has similar interop as Blazor does for DOM manipulation or how robust it is. All of this also relies on the success of WebAssembly as a technology. If that sizzles and is abandoned, these frameworks and libraries will follow suit. JavaScript does seem to be a king with no equal since it has endured while others have fallen to obscurity and were history to repeat itself, it paints no flattering picture for Blazor or its contemporaries.