ASP.NET Core MVC remains a solid choice for building web applications, but what if you want to start using Blazor for some of your projects? How do you migrate from MVC to Blazor and what pitfalls should you look out for along the way?
If you’re planning to take an existing MVC app and migrate all (or parts) of it to Blazor, the key is to break this down into smaller steps; you’re going to find it much easier to migrate an app if you do it in stages.
One sustainable approach is to migrate one view at a time.
Say you wanted to take this user list page and migrate it from MVC to Blazor:
Here’s how I would tackle it:
@code
section for the new component declare a Model
property of the same type as the one the view currently references.When you implement a feature in MVC, you typically create a view.
This view usually represents an entire page in your application.
When someone comes along to your MVC site and requests a page, their request is forwarded to ASP.NET, which finds the relevant controller and invokes the controller action.
MVC locates the relevant view, processes your Razor markup and generates HTML, which is then returned as an HTTP response to the browser.
Here’s how our User List view might look in an MVC app.
Index.cshtml
@model MVCToBlazor.Controllers.UserListModel
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Last Login</th>
<th>Logins (last 30 days)</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model.Users)
{
<tr>
<td>@user.Name</td>
<td>@user.Role</td>
<td>@user.LastLogin</td>
<td>@user.RecentLoginCount</td>
</tr>
}
</tbody>
</table>
With Blazor, things are a little different. Rather than think in terms of entire pages, you are able instead to create smaller components that can be composed together to form a larger feature/page.
However, if you’re migrating an existing view, I’d avoid trying to break it down into smaller components at this stage; better to get the Blazor version up and running first, before making too many changes.
We can easily take the existing User List markup and move it into a new component.
UserList.razor
@page "/users"
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Last Login</th>
<th>Logins (last 30 days)</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model.Users)
{
<tr>
<td>@user.Name</td>
<td>@user.Role</td>
<td>@user.LastLogin</td>
<td>@user.RecentLoginCount</td>
</tr>
}
</tbody>
</table>
When you do this with your own view markup, you may find everything moves seamlessly over to the new component but occasionally you’ll find something that doesn’t work in a Razor component.
Things to watch out for are MVC-specific concepts like references to partial views, etc.
If you encounter a partial view, your best options are to either:
Either way the key is to remove the MVC-specific parts so you can keep moving forward with the migration; it really pays to take whichever path forward seems simplest at this stage.
Now at this point you still can’t test your new component because you’re missing the model!
Your MVC version likely returns a model alongside the view.
UserController.cs
public IActionResult Index()
{
UserListModel userListModel = _userService.ListAll();
return View(userListModel);
}
We don’t have a controller in our Blazor version; instead we have the component itself.
We can declare an instance of this model directly in the component.
UserList.razor
@page "/users"
@using MvcToBlazor.Shared
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Last Login</th>
<th>Logins (last 30 days)</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model.Users)
{
<tr>
<td>@user.Name</td>
<td>@user.Role</td>
<td>@user.LastLogin</td>
<td>@user.RecentLoginCount</td>
</tr>
}
</tbody>
</table>
@code {
private UserListModel Model;
protected override async Task OnInitializedAsync()
{
Model = new UserListModel();
}
}
In a moment we’re going to populate this model from the User Service, just like we did in MVC, but first it’s worth discussing this line:
@using MvcToBlazor.Shared
It makes life much easier when migrating from MVC to Blazor if you can move your models into a shared project.
This way both the MVC and Blazor projects can reference them.
Your best bet is to create a new shared project which targets .NET Standard. Choose the version of .NET Standard which is compatible with both your new Blazor and existing MVC projects.
Check out the official compatibilty chart to see which version that is.
Once you move your models into a shared project, your existing MVC app can still reference them, but so too can your shiny new new Blazor app (as in our example).
Presently we’re just populating a hardcoded instance of the model …
protected override async Task OnInitializedAsync()
{
Model = new UserListModel();
}
This is a useful step to check we can get everything compiling and running. If you want, you can actually hard code some test data here as well, to make sure everything looks right.
Once you’re happy that the markup looks as you’d expect, the next step is to bring in the actual call to retrieve the data.
This is where things vary depending on whether you’re using Blazor WASM or Blazor Server.
If using Blazor Server, you should be able to directly reference the same service you’re using in your MVC project.
You can move your services across to the shared project, along with any other dependent services and classes.
Once there, you can reference them directly in the component.
UserList.razor
@page "/users"
@using MvcToBlazor.Shared
<!-- add this -->
@inject UserService UserService
<!-- rest of markup -->
@code {
private UserListModel Model;
protected override async Task OnInitializedAsync()
{
Model = UserService.ListAll();
}
}
Note, you’ll need to register your dependencies in the Blazor project.
For example, if your MVC application registers UserService
(so that it can be injected into controllers), you’ll need to do the same for the Blazor app, but this time in its Program.cs
.
public static async Task Main(string[] args)
{
// other code
builder.Services.AddScoped<UserService>();
await builder.Build().RunAsync();
}
If you’re migrating to Blazor WASM, the most likely scenario is that you’ll want to expose your existing logic/data access functions via an ASP.NET Web API.
This is because your services still need to run on a server, to access resources like databases, etc. If you put an API on top of that service, you can call it from the browser (in this case using Blazor WASM).
You’ll need to create a Web API controller and action which will, in turn, invoke UserService
to retrieve the data.
Once you have that in place, you can call this API via HTTP from your Blazor component.
First you’ll need to inject an instance of HttpClient
.
UserList.razor
@page "/users"
@using MvcToBlazor.Shared
@inject HttpClient HttpClient
Then call it from your component’s OnInitializedAsync
method.
@code {
private UserListModel Model;
protected override async Task OnInitializedAsync()
{
Model = await HttpClient.GetFromJsonAsync<UserListModel>("api/users");
}
}
One important point here: because you’re essentially wrapping your existing service in an API, you don’t need to move it to the shared project.
In fact, doing so would be a very bad idea for Blazor WASM because that shared project will be bundled up and distributed to the browser when users access your application.
If you put all your services in there, they would be deployed to the browser! Probably not what you’re looking for!
Blazor Server doesn’t encounter this problem because the application (including services) lives on the server and is never shipped to the browser.
That’s it for the migration! You now have a Blazor component equivalent of your MVC view, which retrieves the same data into the same model.
In some regards MVC views and Blazor components are similar. They both use the Razor templating language and so much of their syntax is shared.
As we’ve seen with this example, migrating entire views from MVC to Blazor can be relatively straightforward.
The biggest difference between the two actually lies in how you come to think about building your user interface.
At the heart of moving from MVC to Blazor is a shift to “think in components.”
In many cases, you can break your UI down into smaller components, making pages simpler to comprehend, and removing lots of otherwise duplicated boilerplate code.
So, definitely start with one or two simple views to build some momentum, but for bonus points once you’ve migrated a few views, take a look at your application with fresh eyes and see if you can identify any parts of the UI which could be pulled out into separate components.
If you can locate these and extract them, you’ll really start to benefit from some of Blazor’s key strengths:
Jon spends his days building applications using Microsoft technologies (plus, whisper it quietly, a little bit of JavaScript) and his spare time helping developers level up their skills and knowledge via his blog, courses and books. He's especially passionate about enabling developers to build better web applications by mastering the tools available to them. Follow him on Twitter here.