5 min read

Blazor Code-behind

Justin Yoo

Suppose you've got an ASP.NET WebForm application that has been running for ages. As it's only run on the .NET Framework, and there will be no more new feature added, Microsoft recommends migrating the app to .NET 5. However, for many reasons, migration wouldn't be that easy. Despite those reasons, what could you do for migration from ASP.NET WebForm to .NET 5? There are several alternatives, but the most pragmatic and feasible way that might be able to minimise the migration cost is to move to Blazor.

When you see the Counter.razor file as soon as a new Blazor app is created, it looks like:

@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
view raw 01-counter.razor hosted with ❤ by GitHub

As you can see, both the HTML part and code part co-exist in one single .razor file. It's OK to run the app like that as long as the app works fine. But as the business grows, requirements get complicated, and the size gets bigger, splitting the .razor file into two – HTML document and its code-behind – would become more beneficial. In fact, modern app development also strongly recommends applying the Separation of Concerns principle. Throughout this post, I'm going to discuss how to divide the .razor file into two different files.

Building Partial Class

Basically, all .razor files become the class objects for themselves. In other words, the Counter.razor file mentioned above becomes the Counter class once compiled. Therefore, to separate HTML and code from each other, the code-behind must use the partial modifier when declaring a class (line #1).

public partial class Counter
{
...
}
view raw 02-counter.cs hosted with ❤ by GitHub

Then move the codes inside the @code block to the Counter class.

public partial class Counter
{
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
view raw 03-counter.cs hosted with ❤ by GitHub

Compile the app and run it, and you will see the Blazor app is up and running without an issue.

Inheriting Component Model

Like above, you can use the partial modifier. There is another approach, inheriting a component model class. It brings up more benefits because the component model already provides properties and events which you can utilise. Create a CounterBase class by inheriting the ComponentBase class (line #1).

public class CounterBase : ComponentBase
{
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
view raw 04-counterbase.cs hosted with ❤ by GitHub

Then, add the @inherits directive into the Counter.razor file (line #2):

@page "/counter"
@inherits CounterBase
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
view raw 05-counter.razor hosted with ❤ by GitHub

You will find out the red curly underlines on both @currentCount and IncrementCount that indicate errors.

Error on Counter.razor

It's because both currentCount field IncrementCount() method are in the scope of private. To access both, the scoping must be changed to protected at least. Change the scope from private to protected and update the currentCount field to the CurrentCount property (line #3, 5).

public class CounterBase : ComponentBase
{
protected int CurrentCount { get; set; } = 0;
protected void IncrementCount()
{
this.CurrentCount++;
}
}
view raw 06-counterbase.cs hosted with ❤ by GitHub

Let's add one more thing here. The ComponentBase class contains various methods impacting the component's lifecycle. The OnInitializedAsync() method is one of them. When you look at the method, as a WebForm developer, it looks very similar to either the Page_Init() or Page_Load() method. Let's initiate the CurrentCount property value to 10 (line #10-13).

public class CounterBase : ComponentBase
{
protected int CurrentCount { get; set; } = 0;
protected void IncrementCount()
{
this.CurrentCount++;
}
protected override async Task OnInitializedAsync()
{
this.CurrentCount = 10;
}
}
view raw 07-counterbase.cs hosted with ❤ by GitHub

Then, update the Counter.razor file to refer to the protected property of CurrentCount (line #6) instead of the private field of currentCount. Those red curly underlines have disappeared!

@page "/counter"
@inherits CounterBase
<h1>Counter</h1>
<p>Current count: @CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
view raw 08-counter.razor hosted with ❤ by GitHub

Compile the app and rerun it, and confirm the app works as expected. In addition to that, you will see the Current count value of 10 by default.

Initialised to 10 at Counter.razor

Combination of Partial Class and ComponentBase Inheritance

The downside of using the component model is to use the @inherits directives within the accompanying .razor file. It might be small, but to me, that seems redundant. Can I avoid using the directive? Of course, I can! Just use the partial modifier to the inheriting class like below. Change the class name to Counter and add the partial modifier (line #1).

public partial class Counter : ComponentBase
{
protected int CurrentCount { get; set; } = 0;
protected void IncrementCount()
{
this.CurrentCount++;
}
protected override async Task OnInitializedAsync()
{
this.CurrentCount = 10;
}
}
view raw 09-counter.cs hosted with ❤ by GitHub

Then, remove the @inherits directive from the Counter.razor file.

@page "/counter"
<h1>Counter</h1>
<p>Current count: @CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
view raw 10-counter.razor hosted with ❤ by GitHub

Let's compile the app and run it. Can you see what you expected?


So far, we've walked through how to extract the codes from HTML in a .razor file of a Blazor application. In fact, it's not a tip nor a trick, but a suggestion for a better app development approach. Suppose you or your organisation plans to migrate existing ASP.NET WebForm applications to Blazor and wants to keep the same development experience. In that case, this post will be the starting point.