Converting an ASP.NET Core MVC App to Blazor SPA – Part 2 (The Frontend)

In part 1, I created the backend API and a re-usable client package. In this post, I will start the work on the frontend and get the basics up and running before tightening the screws with authentication and rounding everything up in part 3. This post is not meant to be a Blazor tutorial at all, I am only presenting my experience of using it and highlighting some key pieces, also I am assuming reader familiarity with MVC and Razor.

I opted to go with Blazor for the following very simple and personal reasons:

  1. End to end C# use based on the Razor-ish binding model that is familiar to me as an MVC developer and thanks to WebAssembly, C# can now actually run inside your browser almost natively. For the developer this means the context switch between C# and (Java/Type)Script is minimal. With JSInterop in Blazor its still possible to do JS, but aside from interactions with browser storage, I didn’t really need to! Plus I feel using JSInterop in Blazor is quite clunky and its easy to make mistakes that won’t be caught until your app runs, its almost as bad as using C# reflection in your code to invoke your other C# code without good reason.
    Basic WebAssembly is now standard in almost all browsers with more features on the roadmap, so I believe Blazor will only see an uptake. It might be a bit slow considering tough competition from JS frameworks.
  2. Encourages component driven model of developing user interfaces and event driven model of interacting between components which results in less spaghetti and relatively easier to understand code. Having built a client side event driven architecture in JavaScript from scratch in the past, I know how cumbersome it can get to manage over time if the language doesn’t make such constructs available as first class citizens or have other way of enforcing some of the good practices. State should be passed from parent to child and child should use callbacks or events to talk back to parent!
  3. Blazor seems to have a great eco-system of UX libraries that can be integrated into the app extending its look and feel and really super-charge it. Best part, almost all these components are available as Nuget packages something that .NET devs have been using for ages. No more insufferable pain in the neck that is Node modules (a.k.a bloatware) with its own idiosyncratic CLI tools. For my app I opted to use MatBlazor – a material design library for Blazor! Integration with this library was pretty smooth.
  4. Dependency Injection for the client app based on the same DI model for ASP.NET Core apps resulting in better separation of concerns and looser coupling.
  5. Client side and server side models supported for view rendering and callbacks.

There was only one problem, I didn’t actually know Blazor! So I buckled up to just learn the old fashioned way, by doing it and stumbling my way through!

First things first, how do I load data from the API backend and display it on the view in a table as soon as the page is loaded? Blazor components expose various lifecycle events that you could hook into and execute custom logic. For e.g. in order to load the expenses as soon as that page is loaded, I can override the OnInitializedAsync lifecycle method in a @code {...} block and write code to load data there, like so:

<table class="table">
<thead>
<tr>
<th style="width:10%">Pot</th>
<th style="width:15%">Expense Date</th>
<th>Description</th>
<th>Spend</th>
</tr>
</thead>
<tbody>
@foreach (var expense in expenseLogs)
{
<tr>
<td>@expense.Pot</td>
<td>@expense.ExpenseDate.ToString("dd/MM/yyyy")</td>
<td>@expense.Description</td>
<td>@expense.FormattedSpend</td>
</tr>
}
</tbody>
</table>
@code {
private IReadOnlyCollection<ExpenseLogItem> expenseLogs =
new List<ExpenseLogItem>();
protected override async Task OnInitializedAsync()
{
expenseLogs = await traxpenseApi.GetAllExpenses();
}
}

Blazor can bind to any member you declare in the code block (private or public), here its the expenseLogs field which contains a collection of the ExpenseLogItem ViewModel. This class is essentially a POCO type with getters and setters. The data binding in the view itself will be very familiar to MVC Razor developers because its almost exactly the same.

As you can see on line 11, inside the Razor block, I am looping through all expenseLogs items and rendering a table with rows populated from individual properties of the collection.

Blazor is built around the concept of components i.e. pieces/fragments of view and model code that are loosely coupled and reusable across any page. The child components communicate with parent pages/components by raising events/executing callbacks that parent component tell it to. For e.g. consider the following component, a modal dialog where I can create a new expense:

<MatDialog @bind-IsOpen="@IsDialogOpen">
<MatDialogTitle>Create expense for <b>@SelectedPot.PotName</b></MatDialogTitle>
<MatDialogContent>
<MatDatePicker Label="Expense Date"
Required="true"
@bind-Value="@Expense.Date"
Format="dd/MM/yyyy"></MatDatePicker>
<MatTextField Label="Description"
Required="true"
@bind-Value="@Expense.Description"></MatTextField>
<MatTextField Label="Spend"
Required="true"
@bind-Value="@Expense.Spend"></MatTextField>
</MatDialogContent>
<MatDialogActions>
<MatButton OnClick="@(async e =>
{
await OnDialogClosing.InvokeAsync();
CloseDialog();
})">Cancel</MatButton>
<MatButton OnClick="@(async e =>
{
Expense.PotId = SelectedPot.PotId;
await OnAddNewExpense.InvokeAsync(
new NewExpenseEventArgs(Expense));
})">Save</MatButton>
</MatDialogActions>
</MatDialog>
@code {
[Parameter]
public bool IsDialogOpen { get; set; }
[Parameter]
public SelectedPot SelectedPot { get; set; }
[Parameter]
public EventCallback<NewExpenseEventArgs> OnAddNewExpense { get; set; }
[Parameter]
public EventCallback OnDialogClosing { get; set; }
private NewExpense Expense { get; set; }
= new NewExpense();
private void CloseDialog() => IsDialogOpen = false;
}

Now there is quite a bit going on here so let’s unpack it:

The child AddNewExpense component exposes a few parameters to its parent, these parameters are marked by the [Parameter] attribute. For e.g. the boolean IsDialogOpen parameter will control the opening/closing of the modal, the SelectedPot will pass in the data into this child component from the parent and the EventCallback<NewExpenseEventArgs> will allow the parent to attach a delegate to the child component which the child will invoke when I press the OK button, passing the necessary arguments back to the parent (lines 24-29). This will usually be the way parent and child components communicate in Blazor.

Rendering the child on parent page looks like this (notice lines 22-28):

@page "/budget"
@inject ITraxpenseApiClient traxpenseApi
<!--other stuff-->
<MatTable Items="@CurrentBudget.Pots">
<Row>
<td>
<MatButton Raised="true" OnClick="@(args => OpenAddExpenseDialog(
new SelectedPot
{
PotId = context.Id,
PotName = context.Name
}))">
Add
</MatButton>
</td>
...
</Row>
</MatTable>
<AddNewExpenseModal IsDialogOpen="@isAddExpenseDialogOpen"
SelectedPot="@ActivePot"
OnAddNewExpense="@(async args => await CreateNewExpense(
(args as NewExpenseEventArgs).NewExpense))"
OnDialogClosing="@(async _ => await AddExpenseDialogClosing())">
</AddNewExpenseModal>
@code {
private bool isAddExpenseDialogOpen = false;
private SelectedPot ActivePot { get; set; } =
new SelectedPot();
private Task AddExpenseDialogClosing()
{
isAddExpenseDialogOpen = false;
return Task.CompletedTask;
}
private async Task CreateNewExpense(NewExpense newExpense)
{
await traxpenseApi.CreateNewExpense(newExpense.ToDto());
// exception handling and notification code
}
private void OpenAddExpenseDialog(SelectedPot selectedPot)
{
ActivePot = selectedPot;
isAddExpenseDialogOpen = true;
}
}
view raw Budget.razor hosted with ❤ by GitHub

When the open button is clicked on line 9, it invokes the OpenAddExpenseDialog which in turns sets the flag to true signalling the child modal to open. After filling out all the fields in the child, when you hit “Save”, it will invoke the delegate attached by the parent on line 24 which is a call to CreateNewExpense passing in the expense structure with all the values in it. The parent will then call the backend API to create the expense.

This approach works better than putting direct backend interactions in child components because I don’t have to propagate the dependencies to accomplish those interactions. That responsibility is better left with the parent components/views because it also centralises the dependencies rather than smearing them all over. There might be cases where it might be better to create a fully self contained reusable component for e.g. a map component that talks to mapping API backend but needs input arguments like latitude, longitude, radius, distance etc from the user. In this case I’d still raise events in the map component to let the parent know that the operation completed (for e.g. geo-coding) and pass back the results.

In Blazor the data binding is two-way by default i.e. changes made in a UI control are reflected in the view-model and changes made to the view-model are automatically reflected in the UI control. If you’ve worked with WPF or Silverlight with the MVVM pattern then this would be familiar. It looks like this:

Two way binding in Blazor using ViewModels

Now that things are looking to be taking shape, how do I clean up the code a bit for e.g. use DI to inject the api client etc? Turns out this is very easy as well, in Program.cs I register my api client in the ConfigureServices method (similar to ASP.NET Core):

public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
// other stuff
ConfigureServices(builder.Services);
await builder.Build().RunAsync();
}
public static void ConfigureServices(IServiceCollection services)
{
services.AddScoped(serviceProvider =>
{
return TraxpenseApiClientFactory.CreateClient(
new BrowserAuthTokenRepository(
serviceProvider.GetService<IJSRuntime>()));
});
//...other services
}
}
view raw Bootstrap.cs hosted with ❤ by GitHub

Then I just ask for an instance of this client from my views (see line 3) and I am on my way!

@page "/budget"
@*Inject services into views*@
@inject ITraxpenseApiClient traxpenseApi
<!--view stuff-->
<!-- C# code stuff -->
@code {
// bind this to the view
private CurrentBudget currentBudget;
protected override async Task OnInitializedAsync()
{
currentBudget = await traxpenseApi.GetCurrentBudget();
}
// etc. etc..
}

Quick Deep Dive into Blazor WASM

Let’s look at what WASMy stuff gets generated when a Blazor app is published:

Published artifacts

The _bin folder contains all the application dlls.

The blazor.boot.json contains a list of all dependencies that Blazor needs to load to correctly run your application, it also lets Blazor know if these dependencies should be cached in browser’s cache. They mostly do get cached to improve load times.

The dependencies that Blazor will need to load for your app to function correctly

When you hit the home page of the app, the blazor.webassembly.js gets loaded into the browser (along with the default css and scripts) and then it does the following (and to the best of my understanding, asychronously):

  • Reads the blazor.boot.json file to find out what dependencies need to be loaded (see screenshot above)
  • Loads the WASM compiled dotnet runtime (dotnet.wasm) which is one of the dependencies. This runtime is ultimately responsible for running your application code.
  • Loads the JS interop library dotnet x.y.z.js (in my case its dotnet 5.0.1.js). This allows two way communication between JavaScript and dotnet runtime by exposing appropriate global objects and using .NET Reflection.
  • Instantiates the WebAssembly implementation of the browser with the runtime and starts your app by invoking its entry point defined in Program.cs.
My best estimation of the interactions under the hood

The wasm folder contains the following:

Contents of the wasm folder

The JSInterop script (dotnet.3.2.0.js) and the WASM compiled dotnet runtime (dotnet.wasm) amongst other files like the time zones database. The .wat file is the textual representation of the WASM assembly code that doesn’t get generated by the build process but I converted the .wasm file to a .wat using this online tool to show what it looks like under the hood:

🤯 Its almost like assembly language! This file is around 530k lines of code long!
Blazor WASM bootstrapping script, the WASM compiled Dotnet runtime are amongst the 1.4 MB worth of resources being downloaded

You can dig into the WASM specs here and here is a little online IDE that demonstrates how a program written in C++ can be converted into WASM and run in a browser. BlazorGuy also does a decent job of explaining how Blazor works internally. This is as far as I can go for now!

I have only scratched the surface with this post and taken a few design liberties. Debugging the Blazor app is a bit of a pain in the neck because you have to enable in browser debugging using special shortcuts etc so I am still getting to grips with it. Please checkout the official docs and community links linked here for more in-depth look into Blazor. In part 3, I will cover the final bits about adding authentication to the frontend and wrap up!

One Reply to “Converting an ASP.NET Core MVC App to Blazor SPA – Part 2 (The Frontend)”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.