Blazor is a new web framework that allows you to create fully interactive web apps using C#. Using the magic of a .NET runtime compiled for WebAssembly, you can even run Blazor entirely on the client—you don’t have to use JavaScript frameworks to create your applications anymore.
What Is Blazor?
Blazor is the latest feature of ASP.NET Core, Microsoft’s 20 year old web framework. Despite being old, Microsoft still maintains it, and ASP.NET has been consistently improving along with C# and the .NET runtime as a whole.
Razor pages, and ASP.NET’s old MVC model, both allow you to generate data-driven HTML pages using C#. That’s been around forever, but what it’s always lacked is interactivity. It’s pretty hard to make a web app when you have to reload the page to view changes.
So, Blazor solves that by directly linking the server and client with a library that can modify the DOM at runtime, and brings in many of the component lifecycle methods and update handlers that you’d expect out of a framework intended to compete with React.
And, with Razor page’s minimalist syntax, it makes coding in Blazor a breeze:
Blazor has two main modes of operation, the coolest of which is Blazor Client, which packages .NET itself into a framework that can be run entirely on WebAssembly. WASM is basically MSIL for the web; it’s a lightweight binary instruction format that any language can compile to, even “desktop” languages like C++ and Rust.
So how is performance with this? Well, currently it’s using an interpreter, as native AOT compilation for .NET is still being worked on. But that will improve eventually; the main downside of Blazor Client is longer download times—DLLs can be big files by web standards, and they all need to be loaded to get the application up and running. However, large JS frameworks also have this problem, and is a big reason why server-side rendering is popular.
It makes up for that download time by being very fast to refresh pages and navigate around the app, considering it doesn’t have to wait for the server to do so.
If you want to use a more traditional hosting model similar to React’s Server Side Rendering (SSR), that option is also available to you. It’s more performant, and comes with the benefit of running code on a trusted server.
To achieve this, Blazor Server can connect to the client using a SignalR connection, which is just a fancy wrapper over WebSockets that can also fall back to HTTP if it needs to. This does put more stress on the ASP.NET server than traditional web hosting, as it needs to re-render and re-send every single HTML change to all connected clients. If you have thousands of people connected, this can be an issue when scaling up.
While Blazor is currently a web framework, Microsoft is also making Blazor Desktop, which functions a lot like Electron does by packaging web UIs into desktop applications, as well as Blazor Native, which will be able to render native UIs on mobile operating systems. Microsoft seems to think of it as their next application programming model for making interactive frontends on any platform.
Hybrid Mode
Blazor can also be used in a “hybrid mode” using prerendering. Blazor WASM can display raw HTML while the framework loads, usually used for a loading bar or spinner wheel. But, if you host the app from ASP.NET, you can render the Razor pages from the server before you even send the app to the client.
This works perfectly, with the catch that the page will not become interactive until Blazor actually loads. It usually only takes a second though so this isn’t really an issue, besides some quirks with reloading data twice.
With this setup, you get the best of both worlds— your app loads quickly, with no loading screen, and it’s lightning fast to navigate around. This feature is still being worked on, and comes with a lot of quirks, but as of .NET 6 preview 6, it is working, and you can read more at Jon Hilton’s guides, parts one and two.
One of the quirks is that WASM apps relying on an API will have to have two implementations of each service, on the client and server, so that the server can talk to itself to fetch the information when it pre-renders. This results in some flashing when WASM loads and does its own data fetching, but this can be fixed in .NET 6—this guide shows how to persist component state so that your app will seamlessly transition from the fake “loading page” to the real application.
Setting Up Blazor
Luckily Visual Studio provides generators and you don’t have to set this all up yourself. Open Visual Studio, and create a new project. Search for “Blazor” and select either Blazor WebAssembly or Blazor Server.
We’ll go with Blazor WebAssembly here, as Blazor Server is simpler and just includes both the client and server in the same project.
Give it a name and select your framework. One thing to note is that Blazor WebAssembly technically doesn’t even need a server at all; you can just host it as a static website on NGINX or even an AWS S3 bucket. But, if you’re connecting to an API to talk to a database, you might as well make that API bundled with the client, as you can share code between them and of course use the same language.
You solution will be set up with three projects, a client application, an ASP.NET server API and web host for the client, and a shared library between them.
If you click launch, you’ll see ASP.NET boot up and serve the web page. If you’re using Blazor Server, it will connect to ASP.NET over SignalR to handle interactions.
Of course, Blazor WebAssembly will only send requests to the server when made explicitly by the application.
A Tour Of The Blazor Environment
Let’s take a tour of the application. Starting with the client, the main entry point is Program.cs
, which creates the WebAssemblyHost, adds the root App.razor
component, then builds and runs the app.
Technically, the entry point is wwwroot/index.html
, which loads Blazor’s JavaScript file, shows a loading page while Blazor initializes, and shows an error message if it doesn’t.
App.razor
handles routing of pages using a Router
component. This takes in the assembly and loads all pages marked with the @page name
attribute. You can learn more about routing in Razor pages here.
The router loads the MainLayout.razor
component, which extends LayoutComponentBase. That loads the NavMenu.razor
component alongside the body, and displays your application.
Great! As for the server, it’s a standard ASP.NET application. It creates a host builder, and adds services configured in Startup.cs
. Notably, it configures it for WASM debugging, serves the Blazor Framework files, and configures its own Razor pages and controllers to serve JSON content.
If this was just a Blazor Server app, it wouldn’t need this separate API, and could just fetch data from an internal service. But since it isn’t, it needs to expose that service over the wire, in the form of a ASP.NET ApiController that specifies handler methods for different HTTP actions.
The models for this are shared between client and server, in the shared project.
Fixing Microsoft’s Awful Color Choices
Before sitting down to code, we need to fix something. For some reason, Microsoft’s default color choice for C# HTML directives in Razor pages is using what is perhaps the worst color combination imaginable—light purple text on a light tan background.
Luckily you can change it from Tools > Options:
Under Environment > Fonts and Colors, select “Razor Directive” and change the background color to something more reasonable. You probably still want it to stand out, so I just chose pure black, which will be noticeable but non-intrusive.
You’ll also need to change “HTML Server-Side Script,” (which is technically now an outdated name considering Blazor WebAssembly’s existence).
And with that simple fix, you can save your eyeballs hours of pain.
Serving Dynamic Content
The last file you’ll want to check out is on the client, FetchData.razor
. This is where the client actually consumes the server-side API, with the following data-driven page:
This is a fairly complex Razor page, so it’s a good example for us to break down here.
To start, it specifies a manual override for the page route with @page "/fetchdata"
. It then imports the shared models from the shared project, and also uses dependency injection to give it an HttpClient, which was set up in Program.cs
.
We’ll skip down to the bottom to explain this first—the @code
block contains the actual C# code for this page. In it, there’s a private variable called forecasts
. This is initially null, but when the page is initialized, it makes a web request to grab the data from the API and deserialize it.
When that variable is updated, Blazor detects the change and re-renders the page. The page itself is everything in between, which starts with a header and description, but then contains an @if
statement. This is C# conditional HTML, and in this case is used to show loading text while Blazor is making the request to the API. Then, once it finishes and re-renders, forecasts
won’t be null, and it will render the table that will do @foreach
element in the list, and render a table row.
Using this, you can render dynamic content from your models. To support more advanced CRUD operations, and more dynamic UIs, you will need to use buttons and inputs. You can use <button>
tags with an @onclick
attribute, which takes a method reference.
You can use EventCallbacks to pass the event up to parent components. You can also use Lambda expressions to handle events inline, if the method is short:
<button @onclick="@(e => heading = "New heading!!!")"> Update heading </button>
There are multiple additional event arguments that you can pass to these handler functions, including mouse location, keyboard events, and touch events.
If you want to implement a search box, Blazor has binds for that. However, if you want it to search automatically when you press enter, and not on every keypress, you’ll need to bind the @onkeydown event to a function, which takes a KeyboardEventArgs object, and manually check if Enter was pressed.
You can’t get the input value from the KeyboardEventArgs though, so it results in this messy input box where you must bind onkeydown and oninput, set a string value, and then use that string in onkeydown.
<input type="text" @onkeydown="@Enter" @oninput="@(ui => { searchValue = (string) ui.Value;})" /> @code { public void Enter(KeyboardEventArgs e) { if (e.Code == "Enter" || e.Code == "NumpadEnter") { //do stuff } } }
How Does Styling Work?
Extremely simple. There’s a site.css
master file, but for component level styling, just create a .razor.css
file for each Razor page:
index.razor index.razor.css
Blazor will automatically create a scoped CSS file that applies the settings from each Razor page to only that Razor page:
<link href="https://www.cloudsavvyit.com/11962/how-to-create-c-client-web-apps-with-microsofts-blazor-web-framework/BlazorWebAssemblyTest.Client.styles.css" rel="stylesheet" /> /* /Pages/Counter.razor.rz.scp.css */ h1[b-3xxtam6d07] { color: brown; }
Getting pre-processors like SASS to work is a little more complicated, but completely doable.
Blazor Component Lifecycle
Like React, Blazor also has component lifecycle bindings. As per this diagram from Blazor University, a fantastic resource for learning Blazor, the Blazor runtime calls them in the following order:
SetParametersAsync and OnParametersSet are called whenever the component’s parent renders. OnInitializedAsync is also called here if it’s the first time. Then, for each user interaction, the event can call StateHasChanged, which checks if it should render, and triggers this update process all over again.
There are too many details to list here, so you should read the guide from Blazor University, which explains all of them, as well as weird interactions with async/await methods. To render as much content as fast as possible, Blazor may run StateHasChanged and render twice, once right when the Task awaiter object is returned, and once when it’s finished.
How Does Blazor Work with JavaScript?
Regardless of whether you go with Blazor Server or Blazor Desktop, you have full JavaScript interoperability, such as calling JS functions from managed code:
private async Task ConvertArray() { text = new(await JS.InvokeAsync<string>("convertArray", quoteArray)); }
And calling .NET from JS:
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});
You can actually use all NPM packages with Blazor, though you should prefer a NuGet package most of the time if it’s an option.
Navigating The User Around
One of the most important problems with web apps, especially single page web apps (SPWAs), is routing. Each Razor page has a default route according to its name, but you can override this, and you can also add multiple page routes to each Razor page. The router will match each one separately and still serve the same file.
However, if you want to serve different content depending on what URL you’re on, things get a little complicated. The simplest solution would just be define a @page route for each string you want to add. This works the first time, but because of the way Blazor handles updates, it won’t refresh the page when navigating to a new URL because the parameters haven’t changed. You can still use this to define multiple spellings of the same page, as long as those pages don’t link to each other.
The solution to the SPWA problem is to use parameter based routes whenever you want to catch multiple options and link between them. Parameters can be defined in brackets:
This particular setup is for the root page. If you’re concerned that it’s going to match other pages like /error
since it’s configured to match anything after root, don’t worry, Blazor will match other pages that are strictly defined before falling back to using parameter-based routes. However, you can’t have multiple parameter routes on the same root route without getting an ambiguous route error.
In my case, I wanted to navigate the user to a new page with an argument. You can do this by accessing the NavigationManager and calling NavigateTo:
NavManager.NavigateTo("/search/" + searchValue);
If you want more advanced routing, you’ll need to look into Query Parameters.
Manually Updating The Page On URL Change
One of the quirks of Blazor is that it doesn’t trigger an update when your URL changes, even if you have stateful code that depends on it, so you’ll need to manually update. Luckily, the NavigationManager has a delegate for when the location changes, and you can bind a function to that.
NavigationManager.LocationChanged += HandleLocationChanged;
You’ll want to use InvokeAsync with a function that updates whatever state object your app depends on. Then, you’ll need to call StateHasChanged afterwards to trigger a refresh.
You can also call OnParametersSetAsync from here, if your code also depends on other URL parameters and you don’t want to copy/paste.
Blazor Libraries
One of the benefits of Blazor is that it’s not a brand new thing—it’s built on the 20 year backbone of ASP.NET, and so it already comes with a diverse ecosystem of libraries. We won’t get into a list of them here, because there’s already the awesome-blazor list on GitHub that covers everything.
However, one thing you should definitely be using is a pre-made component library. There are a couple free ones, but RadZen is one of the most popular ones, and it’s completely open source. Blazorise is another great one, though it’s only free for non-commercial products.
In either case, using pre-made component layouts and hand-styling them later will allow for rapid prototyping, and speed up your development time drastically. We highly recommend them.
The Future Of Blazor
Blazor is a truly unique framework among an ocean of JavaScript-based React clones, and with Microsoft behind it, it has a bright future. We mentioned earlier that Microsoft had plans for Blazor Desktop, which is coming in late 2021.
Blazor Desktop will function a lot like Electron does, where it launches your app in a Chromium container with some bindings for desktop functionality. Except, instead of using Chromium, Blazor Desktop is going to use native based WebViews, and run the .NET code directly on the machine.
In the meantime, you can actually get Blazor on the desktop now with Electron.NET, and it works surprisingly well. All you have to do is install it and add Electron as an ASP.NET service. You can also call Electron native functions from C#.