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

Blazor is an exciting new way to be able to write some client-side code in C# instead of JavaScript. Blazor can be rendered either in the user’s browser, or on the server over SignalR. We will be looking into both options. Compared to other Web Assembly options out there Blazor Web Assembly is an entire framework for an application. In this article we will only be exploring the rendering options. The routing will still take place within the MVC framework.

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.

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:

Since we will be exploring the 5 different types of RenderMode’s for components we will create a controller and views for each type as well as an index page to have us browse to either page.
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:

To the Models folder create a new class called “Item.cs” and give it the following code:
namespace WebAssemblyInMVCApp1.Models
{
    public class Item
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    }
}
Add a new controller to the Controllers folder called “ComponentExamples.cs” and give it the following code.
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);
        }
    }
}
Return to your Views\ComponentExamples\Index.cshtml file and give it the following code:
@{
    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>
To the Views\Shared\_Layout.cshtml file add a link to the ComponentExamples Index page by adding the following line of code after the Privacy link:
<li class="nav-item">
         <a class="nav-link text-dark" asp-area="" asp-controller="ComponentExamples" asp-action="Index">Component Examples</a>
</li>
This should set us up to be able to browse to the ComponentExamples home page, and then have links to the various render modes that we will be demonstrating. For now, let’s just add some “Coming Soon” content to each of the ComponentExamples.

Views\ComponentExamples\Server.cshtml

@{
    ViewData["Title"] = "Component Example - RenderMode.Server";
}

<h1>@ViewData["Title"]</h1>

<p>Coming Soon!</p>
Views\ComponentExamples\ServerPrerendered.cshtml
@{
    ViewData["Title"] = "Component Example - RenderMode.ServerPrerendered";
}

<h1>@ViewData["Title"]</h1>

<p>Coming Soon!</p>
Views\ComponentExamples\Static.cshtml
@{
    ViewData["Title"] = "Component Example - RenderMode.Static";
}

<h1>@ViewData["Title"]</h1>

<p>Coming Soon!</p>
Views\ComponentExamples\WebAssembly.cshtml
@{
    ViewData["Title"] = "Component Example - RenderMode.WebAssembly";
}

<h1>@ViewData["Title"]</h1>

<p>Coming Soon!</p>
Views\ComponentExamples\WebAssemblyPrerendered.cshtml
@{
    ViewData["Title"] = "Component Example - RenderMode.WebAssemblyPrerendered";
}

<h1>@ViewData["Title"]</h1>

<p>Coming Soon!</p>
At this point we can debug the application and browse through it to see each of these pages in their “Coming Soon” state.

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:

Give ItemComponent.Razor the following code. We will use it to explorer the impacts of the different rendering modes.
<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++;
    }
}
Give Views\ComponentExamples\Static.cshtml the following code:
@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 })
)
If you run and debug the application and browse through Component Examples and the Static component example link you will see that it has the expected output. With one exception. When you click the button nothing happens. That is because in static mode, it only renders the component html as is, and no interactivity is available. That is why we will go in to the Server and WebAssembly render modes.

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>
}
This is only to demonstrate the possibility. Ideally if you were going to have a statically rendered component then you would not add the button interaction code to the component, rather we would use JavaScript for the button code. We have the code in both places in this example to demonstrate that the code in the component is not used in the static render mode but is used in the other ones as we will soon see.

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();
We also need to bootstrap the blazor renderer. In other examples online you may notice that they recommend adding the blazor server framework javascript to the _Layout.cshtml file, which is valid advice if you plan to have Blazor Server Rendered components on most of your pages. However, in this case we are demonstrating all sorts of render modes, and on different pages, so we will in this case inject the appropriate scripts only on the pages that reflect the render mode that we are using.

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
Update the Views\ComponentExamples\Server.cshtml with the following code:
@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>
}
Run and debug the application, and then go in to the Component Examples, and click on the RenderMode.Server link. Click the button and you will see that the button works right away for the current count.

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.

Furthermore, when we look at the source code on the page, we see this where we had our RenderComponentAsync executed. Instead of the HTML like it was with Static. This time it has a big comment block.
But somehow when looking at the elements in the debugger the html bits are rendering.
Most of this “Magic” is happening within that _framework/blazor.server.js file. Essentially when that JavaScript file loads it checks the base application address, then starts to look for those comment blocks. If it finds one matching a specific call out for it (as we can see, this Blazor type was rendered as a server, then it takes action with the information supplied in that descriptor property. The descriptor is some base64 encoded value that tells the server which component to render. Now all interactions with that component are sent over SignalR (those web socket connections that we saw earlier which were made possible by the AddServerSideBlazor and MapBlazorHub code we added to the Program.cs) and the output is rendered on the screen to that DOM area that the Blazor server framework JavaScript file created after finding the commented space to execute.

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>
}
Debug and browse to this ServerPrerendered page. If we check out what’s going on in the browser, we will notice the same web socket connections and such. But the source code is slightly different. Instead of just the comment block we will also see that a default version of the component was rendered. It didn’t have the data that we supplied, but it did have something as shown in this screenshot.
Furthermore, the button does still work with the C# code that we created earlier.

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:

For this demonstration the name of the BlazorApp doesn’t matter so I will just use the default “BlazorApp1”, and click on Next:
On the next screen accept the defaults and then click on “Create”. Your solution will now have a BlazorApp1 project in it.

To the WebAssemblyInMVCApp1, expand the dependencies and add your BlazorApp1 as a project dependency.

Your project should look something like this now:

There are also several files from wwwroot in each project that conflict with each other. Let’s delete those from the BlazorApp1 project:

    • 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.

After that is installed add app.UseBlazorFrameworkFiles(); to your Startup.cs file. This is what my Startup.cs file looks like:
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();
Furthermore, in my testing the DLLs in the WebAssembly project, in this case BlazorApp1 are the only ones compiled into the build of DLLs that the WebAssembly project can load into the browser. Therefore, we must declare our components within the BlazorApp1 project, instead of inside of the Views\Shared\Components folder like we have been with the static and Server rendered components.

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>
}
Take note that this time we are using the RenderMode.WebAssembly enum, and we are referencing a component within the BlazorApp1 project; furthermore, we are loading blazor.webassembly.js instead of blazor.server.js.

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).

Clicking the button here does work as well with no further JavaScript code.

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>
}
This is like what we would expect after experiencing the ServerPrerender. The comment tag is created, and the prerender is done right in the browser as opposed to only generating the comment tag for the component.

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.

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. Intertech 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 Intertech 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, Intertech is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.