The purpose of this document is to go over the various options to add Blazor to an existing MVC Application.
A Deep Dive into Html.RenderComponentAsync with ASP.NET Core MVC
How to add Blazor Server or Blazor WebAssembly To An Existing MVC Application
If you ever were in a Razor page and investigating the concept of components in Razor, you may have noticed that they have a couple of render modes. So, what is that all about? We will go into that and discuss it further.
Just show me the code: https://github.com/woodman231/WebAssemblyInMVCApp1
Create a new MVC Application
I will be using Visual Studio 2021 to Create a new project using the ASP.NET Core Web App (Model-View-Controller) template. I will be calling my application WebAssemblyInMVCApp1.
When you first create your solution, it should look something like this:
To begin, create a new folder within the Views folder called “ComponentExamples”. Then create 6 Razor pages:
- Index.cshtml
- Server.cshtml
- ServerPrerendered.cshtml
- Static.cshtml
- WebAssembly.cshtml
- WebAssemblyPrerendered.cshtml
Your project should look something like this:
namespace WebAssemblyInMVCApp1.Models
{
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
}
using Microsoft.AspNetCore.Mvc;
using WebAssemblyInMVCApp1.Models;
namespace WebAssemblyInMVCApp1.Controllers
{
public class ComponentExamples : Controller
{
private Item item1 = new Item()
{
Id = 1,
Name = "Name",
Description = "Description",
};
public IActionResult Index()
{
return View();
}
public IActionResult Server()
{
return View(item1);
}
public IActionResult ServerPrerendered()
{
return View(item1);
}
public IActionResult Static()
{
return View(item1);
}
public IActionResult WebAssembly()
{
return View(item1);
}
public IActionResult WebAssemblyPrerendered()
{
return View(item1);
}
}
}
@{
ViewData["Title"] = "Component Examples - Index";
}
<h1>@ViewData["Title"]</h1>
<ul>
<li><a asp-action="Server">Server</a></li>
<li><a asp-action="ServerPrerendered">Server Prerendered</a></li>
<li><a asp-action="Static">Static</a></li>
<li><a asp-action="WebAssembly">Web Assembly</a></li>
<li><a asp-action="WebAssemblyPrerendered">Web Assembly Prerendered</a></li>
</ul>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="ComponentExamples" asp-action="Index">Component Examples</a>
</li>
Views\ComponentExamples\Server.cshtml
@{
ViewData["Title"] = "Component Example - RenderMode.Server";
}
<h1>@ViewData["Title"]</h1>
<p>Coming Soon!</p>
@{
ViewData["Title"] = "Component Example - RenderMode.ServerPrerendered";
}
<h1>@ViewData["Title"]</h1>
<p>Coming Soon!</p>
@{
ViewData["Title"] = "Component Example - RenderMode.Static";
}
<h1>@ViewData["Title"]</h1>
<p>Coming Soon!</p>
@{
ViewData["Title"] = "Component Example - RenderMode.WebAssembly";
}
<h1>@ViewData["Title"]</h1>
<p>Coming Soon!</p>
@{
ViewData["Title"] = "Component Example - RenderMode.WebAssemblyPrerendered";
}
<h1>@ViewData["Title"]</h1>
<p>Coming Soon!</p>
RenderMode.Static
The “Static” render is mode is already Out of the Box within the MVC Framework, so we will start there as there are no dependencies to install or configurations to make. We can just add our first component and go with it.
We will also use an example of passing data to it, even though that is not necessary in all cases.
To the Views\Shared folder add a new folder called: “Components”. Do a right-click on the newly created folder and select Add -> Razor Component. Name the Razor component ItemComponent.Razor.
Your project should look something like this now:
<h3>ItemComponent</h3>
@if(ItemDetails is not null)
{
<dl>
<dt>Id</dt>
<dd>@ItemDetails.Id</dd>
<dt>Name</dt>
<dd>@ItemDetails.Name</dd>
<dt>Description</dt>
<dd>@ItemDetails.Description</dd>
</dl>
<button id="CurrentCountButton" class="btn btn-primary" @onclick="CurrentCountClick">Current Count: @currentCount</button>
}
@code {
[Parameter]
public WebAssemblyInMVCApp1.Models.Item? ItemDetails { get; set; }
private int currentCount = 0;
protected override void OnInitialized()
{
base.OnInitialized();
}
private void CurrentCountClick()
{
currentCount++;
}
}
@model Item;
@{
ViewData["Title"] = "Component Example - RenderMode.Static";
}
<h1>@ViewData["Title"]</h1>
@await(
Html.RenderComponentAsync<WebAssemblyInMVCApp1.Views.Shared.Components.ItemComponent>(RenderMode.Static, new { ItemDetails = Model })
)
I would also like to point out that not all is lost in interactivity with static components. Because you can still program in some JavaScript to make the button work. Update the code in Views\ComponentExamples\Static.cshtml to the following and you will see that the button does work next time that you debug the application.
@model Item;
@{
ViewData["Title"] = "Component Example - RenderMode.Static";
}
<h1>@ViewData["Title"]</h1>
@await(
Html.RenderComponentAsync<WebAssemblyInMVCApp1.Views.Shared.Components.ItemComponent>(RenderMode.Static, new { ItemDetails = Model })
)
@section Scripts {
<script type="text/javascript">
var currentCount = 0;
var currentCountButton = document.getElementById('CurrentCountButton');
currentCountButton.addEventListener('click', function() {
currentCount++;
currentCountButton.textContent = "Current Count: " + currentCount;
});
</script>
}
RenderMode.Server
The RenderMode.Server does allow for client side code to be written in C#, however we do need to add somethings to our project in order for it to work properly.
To begin add the “Microsoft.AspNetCore.Components” nuget package to your WebAssemblyInMVCApp1 project.
Update your Program.cs to have the following references to builder.Services.AddServerSideBlazor(); and app.MapBlazorHub();
My Program.cs looks like this:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddServerSideBlazor();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapBlazorHub();
app.Run();
To the Views\Shared\Components folder add a new _Imports.Razor file and give it the following code. This will make it so that all of these @usings are implied to be inside of each component, in effect the _Imports.Razor is imposed to the top of the code of every component in the Components folder.
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using System.IO
@model Item;
@{
ViewData["Title"] = "Component Example - RenderMode.Server";
}
<h1>@ViewData["Title"]</h1>
@await(
Html.RenderComponentAsync<WebAssemblyInMVCApp1.Views.Shared.Components.ItemComponent>(RenderMode.Server, new { ItemDetails = Model })
)
@section Scripts {
<base href="~/" />
<script src="_framework/blazor.server.js"></script>
}
There is allot going on here as well. For example, if we have our browser debugger on, and look at the network tab. We will see a few websocket connections now.
Thank goodness we have these RenderComponentAsync commands. Could you imagine trying to manually create these comment blocks that blazor.server.js would understand?
RenderMode.ServerPrerendered
No additional dependencies to add here since we already have the RenderMode.Server working.
Update the Views\ComponentExamples\ServerPrerendered.cshtml with the following code:
@model Item;
@{
ViewData["Title"] = "Component Example - RenderMode.ServerPrerendered";
}
<h1>@ViewData["Title"]</h1>
@await(
Html.RenderComponentAsync<WebAssemblyInMVCApp1.Views.Shared.Components.ItemComponent>(RenderMode.ServerPrerendered, new { ItemDetails = Model })
)
@section Scripts {
<base href="~/" />
<script src="_framework/blazor.server.js"></script>
}
So, what is the difference between that Server and ServerPrerendered, and why would we ever do this? For one example, it would be for SEO. Search engines will clearly not know what these comments mean and will basically have no content to index if that is all that is rendered. This prerender will at least have some content, and then all interactions and updates can be loaded later. So granted this might appear to be “slower” to some humans, it will at least be “complete” to a search engine crawler. Speaking of which it also depends on where do you want the performance to impact the user? Do you want the user to be able to use the component as soon as the page loads, or do you want the page to load as fast as possible, and maybe have the component tell the user that it is loading as you are gathering some state for them? Which experience do you think your users would prefer? If you want the fast as possible page loads, then use Server render. If you want to do the component to be available as soon as the page loads, then use the ServerPrerendered.
RenderMode.WebAssembly
Just like React and Angular can render components in the browser with JavaScript, Blazor Web Assembly can also render components in the browser as well. In the previous two render modes that we discussed the rendering was happening on the server, and the new bits were transmitted over SignalR / web socket connections. WebAssembly mode does not require a SignalR connection, and all rendering is done in the browser as we will demonstrate.
The biggest trick is to get our server to render the /_framework/blazor.webassembly.js file. It will not do it unless a project within the solution has an SDK target of “Microsoft.NET.Sdk.BlazorWebAssembly” and has the NuGet package of “Microsoft.AspNetCore.Components.WebAssembly.Server”. The only way that I have found that really allows for this to happen with the least amount of friction is to add a Blazor Web Assembly project to the solution and tear it down a bit. There may be more graceful ways of doing this, but at this time this is the only one that I am aware of.
Right click on your solution and select Add -> New Project.
Select that you want to add a Blazor Web Assembly App and then click on Next:
To the WebAssemblyInMVCApp1, expand the dependencies and add your BlazorApp1 as a project dependency.
Your project should look something like this now:
- favicon.ico
Right click on the MVC Project and select Manage NuGet packages. Install the “Microsoft.AspNetCore.Components.WebAssembly.Server” NuGet package to this project.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddServerSideBlazor();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.UseBlazorFrameworkFiles();
app.MapBlazorHub();
app.Run();
Fortunately, the WebAssembly project that we imported has a default set of components. We will use the Counter page component in our App for demonstration purposes.
Update the code for Views\ComponentExamples\WebAssembly.cshtml to the following:
@model Item;
@{
ViewData["Title"] = "Component Example - RenderMode.WebAssembly";
}
<h1>@ViewData["Title"]</h1>
@await(
Html.RenderComponentAsync<BlazorApp1.Pages.Counter>(RenderMode.WebAssembly)
)
@section Scripts {
<base href="~/" />
<script src="_framework/blazor.webassembly.js"></script>
}
Debug and run the program again and go in to the RenderMode.WebAssembly page.
You will notice that an error does occur in the browser which complains about not being able to find a component with the id of “app”. This is fair as what the blazor.webassembly.js file does when it is loaded is tries to find an element on the page with that tag, and then looks for the commented code similar to that we saw with the server, only this time the component that it finds looks like this. And as you can see have no prerendering (at this time).
Blazor Web Assembly does its own layouts and routing. We will not be taking advantage of those features in this demonstration and sort of clashes with MVC anyways (Server-side routing vs Client-side routing for example). So, in our case there is no need to have an app component. Our “root” component becomes the counter page component. This is most likely why Microsoft made the project template for Web Assembly App to be able to be a .NET Core hosted app to take care of these complexities for you. Again, the purpose of this demonstration was adding Web Assembly to an existing MVC app, and not creating a new .NET Core hosted Web Assembly app from scratch, although that is a very useful template and I highly recommend it.
RenderMode.WebAssemblyPrerendered
Update the code in Views\ComponentExamples\WebAssemblyPrerendered.cshtml
@model Item;
@{
ViewData["Title"] = "Component Example - RenderMode.WebAssemblyPrerendered";
}
<h1>@ViewData["Title"]</h1>
@await(
Html.RenderComponentAsync<BlazorApp1.Pages.Counter>(RenderMode.WebAssemblyPrerendered)
)
@section Scripts {
<base href="~/" />
<script src="_framework/blazor.webassembly.js"></script>
}
Conclusion
MVC offers many ways of rendering components. Which one is best for you will depend on the expected performance of your application, and resources of your users. For example, will your users have less powerful mobile phones? Then you may want to consider server-side rendering of the components. Will the users have more powerful desktops? Then you may want to consider WebAssembly rendering and give your server a break since the clients can handle the rendering themselves. Furthermore, what are your SEO expectations? What needs to be rendered on the screen vs what needs to be rendered to the user, and when? All things for you to consider as you’re designing your application. There is not a one size fits all which is why there are 5 different ways to render these components are available.
One example of how you could also use all 3 different types (Static, Server, WebAssembly, and then you decided to Prerender or not) would be to use static rendering for Blog Article Content, Web Assembly Rendering for the blog author’s edit form (since the blog author’s will likely be using desktops or laptops), and then Server rendering for users that are going to leave comments on the blog since you will not know if they are using desktops, or mobile phones. The possibilities are endless and gives you allot of control over performance and experience.
Just show me the code: https://github.com/woodman231/WebAssemblyInMVCApp1
About Intertech
Intertech is a Software Development Consulting Firm that provides single and multiple turnkey software development teams, available on your schedule and configured to achieve success as defined by your requirements independently or in co-development with your team. Blackslate Software teams combine proven full-stack, DevOps, Agile-experienced lead consultants with Delivery Management, User Experience, Software Development, and QA experts in Business Process Automation (BPA), Microservices, Client- and Server-Side Web Frameworks of multiple technologies, Custom Portal and Dashboard development, Cloud Integration and Migration (Azure and AWS), and so much more. Each Blackslate Software employee leads with the soft skills necessary to explain complex concepts to stakeholders and team members alike and makes your business more efficient, your data more valuable, and your team better. In addition, Blackslate Software is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.