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++; | |
| } | |
| } |
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.
- This post is based on the doc, ASP.NET Core Razor Components – Partial Class Support.
- The term, Blazor, points to both Blazor Server and Blazor Web Assembly in general unless specifically mentioned.
- You can download the sample code from this GitHub repository.
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 | |
| { | |
| ... | |
| } |
Then move the codes inside the @code block to the Counter class.
| public partial class Counter | |
| { | |
| private int currentCount = 0; | |
| private void IncrementCount() | |
| { | |
| currentCount++; | |
| } | |
| } |
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++; | |
| } | |
| } |
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> |
You will find out the red curly underlines on both @currentCount and IncrementCount that indicate errors.

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++; | |
| } | |
| } |
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; | |
| } | |
| } |
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> |
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.

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; | |
| } | |
| } |
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> |
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.