Can You Use Blazor for Only Part of an App?

Aaron Powell - Dec 12 '19 - - Dev Community

Blazor is designed to be a platform where you create a complete web application and we saw that in the last experiment with Blazor where we created a stand-alone search site for my blog. But like any tool in our toolbox, it isn't always the right one for the job.

Take my blog for example, it's pretty much a read-only site with the content stored in GitHub as markdown that I use Hugo to convert into HTML files. Now sure, it's possible to do it as a Blazor WASM application, we could get a .NET Markdown library could be used and the pages generated on-the-fly, but that'd an inefficient way to have my website run and would provide a sub-optimal experience to readers.

But if we want to integrate the search app that we've previously built, how do we go about that?

Understanding How Blazor Starts

To think about how we can run Blazor WebAssembly within another application we need to learn a bit about how a Blazor WebAssembly application runs.

When you create a new project there's a file called wwwroot/index.html that you might never have dug into, but this is an important piece of the puzzle. It looks like this:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Project Name</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>Loading...</app>

    <script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

And really, it's pretty simple, the important pieces that we need are these two lines:

<app>Loading...</app>

<script src="_framework/blazor.webassembly.js"></script>
Enter fullscreen mode Exit fullscreen mode

We'll get to the <app> element shortly, but first, let's take a look at the JavaScript file. You might notice that this file doesn't appear anywhere on disk, and that's because it's part of the build output. You can find the source of this on GitHub in the ASP.NET Core repository at src/Components/Web.JS/src/Boot.WebAssembly.ts (at the time of writing anyway). This file shares some stuff in common with Blazor Server, but with the main difference of using the MonoPlatform which does a bunch of WASM interop.

This file is critical, without it your Blazor application won't ever start up since it's responsible for initializing the WASM environment that hosts Mono (by injecting a script into the DOM) and then it will use another generated file, _framework/blazor.boot.json, to work out what .NET DLL's will need to be loaded into the Mono/WASM environment.

So you need to have this JS file included and the _framework folder needs to be at the root since that's how it finds the JSON file (see this comment).

Lazy-Loading Blazor

An interesting aside which I came across while digging in the source is that you can delay the load of Blazor by adding autostart="false" to the <script> tag, as per this line and then call window.Blazor.start() in JavaScript to start the Blazor application.

I'm not going to use it for this integration, but it's good to know that you can have a user-initiated initialisation, rather than on page load.

Placing Your Blazor App

Now that we understand what makes our Blazor app start, how do we know where in the DOM it'll appear? Well, that's what the <app> element in our HTML is for, but how does Blazor know about it?

It turns out that that is something that we control from our Startup class:

using Microsoft.AspNetCore.Components.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace DemoProject
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IComponentsApplicationBuilder app)
        {
            app.AddComponent<App>("app");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

See how we're using AddComponent and specifying a DOM selector of app? That's how it knows what element in the DOM the application will start. This is something that you can change, maybe make it a selector to a ID of a DOM element or to a <div>, or to anything else that you want, but it's not that important, so I just leave it as <app>.

Aside: I haven't tried it yet, but given that you specify the DOM element and the entry component (via generics, this points to App.razor in the above sample) you could potentially have multiple independent Blazor apps running on a page. Why would you do this, I have no idea... but you can in theory!

Hosting Blazor

When it comes to hosting Blazor WASM there are a few options but I want to focus on the Azure Storage static sites approach, which is how my blog is hosted.

First thing we'll need to do is publish the app in Release mode using dotnet publish --configuration Release. From that we'll grab the contents of the bin/Release/{TARGET FRAMEWORK}/publish/{ASSEMBLY NAME}/dist/_framework folder, which will contain blazor.boot.json, blazor.server.js, blazor.webassembly.js, a folder called _bin and a folder called wasm.

We want to copy this _framework folder and place it in the root of our static site, maintaining all the paths so that Blazor can start up.

Note: According to the docs you can change the content-root and path-base when hosting using dotnet run but I haven't found them working when it's published. Also, Hugo is very aggressive at setting absolute paths so I found it easiest to put my WASM files in the same structure that dotnet run used.

Since this is a search application let's create a new page called Search and put in our required HTML:

<app></app>

<script src="/_framework/blazor.webassembly.js"></script>
Enter fullscreen mode Exit fullscreen mode

Now generate your static site (or whatever host you're using) and navigate to the /search router.

If everything has gone correctly you'll have just received an error!

Sorry, there's nothing at this address.

D'oh

Blazor Routing

If you remember back to our last post we learnt about the @page directive in Razor Components. Here you specify the route that the page will match on and up until now we've had @page "/" there. But, we're now on /search and Blazor's routing engine has looked at the URL and executed your App.razor component:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
Enter fullscreen mode Exit fullscreen mode

Since the Router didn't find a matched route to use RouteView against it's fallen through to NotFound and that is why we have this error!

Don't worry, it's an easy fix, just update the @page directive to match the route that you want it to match on in your published site or simplify your App.razor to not care about routing.

Once a new publish is done and the files copied across it'll be happy.

Conclusion

Blazor is a great way which we can build rich applications, but there is value in generating static content upfront and using Blazor to enhance an application rather than own it.

Here we've taken a bit of a look at the important files used to run a Blazor application within an HTML page and we've also looked at what it takes to drop it into some other kind of application.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .