ASP.NET Core is a fantastic way to write web applications! It is fast, ergonomic and with the entirety of the .NET ecosystem behind it, I would definitely put it in the conversations as the best way to write the server-side aspect of web applications. When using ASP.NET Core, we have a plethora of options for the front-end of our web apps. There are MVC/Razor Pages and client-side technologies such as React and Angular. The newest kid on the block is Blazor and it has got a huge upgrade in .NET 8. Let’s have a look at it.
Blazor?
For those that are not familiar with Blazor, it is analogous to something like React or any of the popular javascript frameworks but for the .NET world. You can build dynamic UIs for web applications from small reusable components, but rather than javascript, Blazor components are authored using C#. This makes Blazor look particularly attractive to developers who prefer to develop their applications using C#, especially with the compounding complexity of the javascript ecosystem. Up until .NET 8 there have been two ways to use Blazor, Blazor Web Assembly and Blazor Server. You can think of Blazor Web Assembly as almost identical to a single page application (SPA) built with a javascript framework where all the code is executed in the browser and this is what builds up the UI. The only difference is that you send a WebAssembly file to the browser rather than a javascript file. The second way of working with Blazor is through Blazor Server. Blazor Server is similar to Phoenix LiveView from the Elixir ecosystem or Livewire from the Laravel ecosystem. When the application is requested, a SignalR WebSocket connection is opened with the client, and as the user interacts with the application, messages are sent to the ASP.NET core server over the WebSocket, it will process the interaction and it will send back the changes that should be made to the DOM.
@* a basic blazor counter component *@
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
UI decision tree
Understanding where Blazor fits in can be nicely illustrated by a decision tree of how you could decide what UI technology to use for an application you are building. For this comparison let’s consider the following options, MVC, Blazor, JavaScript SPA, and a JavaScript full-stack framework.
ASP.NET Core MVC (Model, View, Controller)
MVC is a tried and tested solution that has been around for close to 15 years. A request is routed to a controller, which builds up a model often by making some sort of database calls. That model is then passed to a view which renders a HTML response to send to the client. It is a simple, yet powerful pattern that is still popular today.
Blazor
As discussed, see above.
JavaScript SPA
Using a JavaScript SPA such as React or Angular has become the most popular way of building the front end of a web application in the last 8 years. If using a SPA, you will usually just have an ASP.NET Web API for the backend. Often, the SPA will be decoupled from the backend. Some people see this as a positive having a clear separation of concerns between the front-end and back-end. I don’t necessarily agree with this, but that is a topic for another post.
JavaScript full-stack framework
Fullstack JavaScript frameworks such as Next.js or SvelteKit have also been rising in popularity. I am not sure how popular they are in .NET circles, likely because they increase hosting complexity as some type of server-side javascript runtime is needed, but I have included them anyway because they are relevant to the changes that have been made in .NET 8 for Blazor. These frameworks have both a frontend and backend portion and blur the lines between the client and the server. If using one of these frameworks, you would likely have a similar setup to the SPA approach with just an ASP.NET Web API for the backend. Fullstack frameworks are usually opinionated, provide features like server-side rendering and hydration out of the box, and can often produce better experiences for users with less developer effort.
A note on the use of JavaScript SPAs and full-stack frameworks, it means that you need to use the JavaScript/TypeScript ecosystem which is usually what C# developers are trying to avoid.
If I was going to be either choosing or recommending a UI option it would look like this.
A quick note, You can combine MVC (the node in green) with frontend tools like HTMX and Alpine and make quite interactive apps. I suggest looking into it if you are interested.
On to Blazor in .NET 8
The first feature that is introduced for Blazor is static server-side rendering. This allows you to render Blazor components to HTML on the server to send in the response just like with MVC. The advantages of rendering HTML on the server is that content is displayed faster, it is SEO-friendly and accessible by default. This means that static server rendering can be used to build content-driven sites that are SEO-friendly, as well as provide a snappier experience for interactive apps. But let’s say you really like the MVC pattern. You love the separation of concerns, and the thought of including controller or model logic in the page/component gives you nightmares! Well good news, there is still might be something for you. It is now possible to render a Blazor from a controller with the new RazorComponetResult which means you could just replace the templating with the nicer Razor component syntax. Here is a video of someone doing just that.
And we’re not done yet, Blazor SSR has a couple more tricks up its sleeve to further improve the experience. When using Blazor SSR, you should include the ‘blazor.web.js’ file. This small bit of JavaScript begins to progressively enhance the static application. The first is by providing enhanced navigations, this means when navigating through pages in the application or submitting forms, it does not perform a traditional browser navigation, instead, it performs a fetch request for the page and intelligently inserts it into the DOM. This does improve the experience, and makes the site feel more like a single-page application. The second enhancement is stream rendering. Stream rendering allows for a part of the app to be returned such as the shell. Meanwhile the server is still performing some sort of asynchronous request or database call for the data needed for the page. Once the data is available, the final page is streamed down to the client. This is a seriously cool feature, it gives the user a near-instant response, making apps feel incredibly snappy and responsive.
@* a blazor component that will show 'loading...' for 500ms then '42'*@
@* requires no interactivity only 'blazor.web.js' file *@
@attribute [StreamRendering(true)]
<p>@(meaningOfLife ?? "loading...")</p>
@code {
private string? meaningOfLife;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
meaningOfLife = "42";
}
}
Now you might be tempted to say that server-side rendering is cool and all but really it is just Razor Pages with a lick of paint and a couple of nice enhancements, nothing revolutionary. The really exciting part is when you start mixing Blazor rendering modes at a per-component basis. Up until .NET 8, when you created a new Blazor project, you had to choose rendering mode, and your entire app would be in either Blazor Web Assembly or Blazor Server. That has all changed with .NET 8, we now can pick the rendering mode at a per-component basis. This is awesome as it means all components are statically rendered by default and then we can opt into interactivity when we need it. This kind of interactivity approach is called an islands architecture because we just have ‘islands’ of interactivity in an otherwise statically rendered application. This type of architecture was popularised by the Astro framework which itself is a joy to use and is what is powering this blog right now.
So what would an application like this look like? You could imagine a UI that looks something like the following, with the interactive parts of the page pictured in green and the rest of the page in black. There could be another page that is entirely statically rendered or one that is entirely interactive, that is the beauty of this architecture.
@* counter component again declaring that it is using web assembly interactivity *@
@* other options InteractiveServer, InteractiveAuto *@
@rendermode InteractiveWebAssembly
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
So are there any downsides to this? For Blazor Server it’s mostly that web sockets are still required which adds to the complexity of hosting your application as well as being more problematic to scale. For Blazor Web Assembly, it’s that we still need to download and initialize the web assembly runtime which is still not fast by any stretch of the imagination. However, there are things that we can do to mitigate this. We can still server-side render our interactive components which means that the user can see the component in the browser however it will only become interactive once the runtime is initialized. We can also use the auto render mode which will use Blazor Server interactivity whilst the web assembly runtime is downloading and then switch to web assembly once it is available.
In my opinion, the sweet spot at the moment is using server-side rendering where possible and bringing in web assembly interactivity when you need it. It’s enjoyable to work with and provides a great user experience. I think that as the technology matures, we will see improvements in bundle size and initialization time. I prefer to stay away from Blazor Server and the Auto render modes due to the hosting complexity and I have found that the user experience starts to suffer when latency increases.
But what if you have a existing MVC or Razor pages application? The good news is Blazor is still easy to introduce! You can render Blazor components from views using the component tag helper. This works with all three modes as well, SSR, Blazor Server, and Web Assembly.
@* render the blazor component 'MyBlazorComponent' using WebAssemblyPrerendered mode *@
<component type="typeof(MyBlazorComponent)" render-mode="WebAssemblyPrerendered" />
So what does the UI decision tree look like now?
With the changes in .NET 8, we get an experience comparable to a full-stack JavaScript framework all while staying in .NET! So what would my decision tree look like now?
It’s Blazor all the way down baby!
Obviously, there is still some nuance to this, you may have dependencies that need a whole lot of JavaScript in which another approach may make more sense. But don’t let that distract you from the fact that Blazor in .NET 8 is a fantastic way of building web application UIs.
Thanks for checking out my post please feel free to share it and connect with me on github or twitter, links in bottom right!