Blazor WebAssembly with .Net Framework
The Problem You have a legacy application running on .Net Framework. It performs reasonably well despite its age, and it is unlikely to go out of support with Microsoft anytime soon. But with .NET 8 (and now 9) well-established, there are many improvements in code quality, security, speed and even new language features that you are restricted from using with .NET Framework. There are various approaches to "upgrading" a .Net Framework application. One method is the "Strangler Pattern" whereby you carve off a piece of the legacy codebase, rewrite it, and inject it back into the application somehow. So, how can we leverage the power of Blazor WASM to take over functionality that is current provided by old technology on .NET Framework? Blazor WebAssembly Fundamentally, when you run a Blazor WASM application, it is bootstrapped by a Javascript file which in turn downloads the necessary WASM binaries from the compiled application. In debug, these binaries can be relatively large, but when compiled at release time, the binaries can shrink a lot due to tree-shaking and AOT compilation. Since the Blazor WASM appears to the browser as like a Javascript application, we should be able to build a Blazor WASM application and then surface it inside the .NET Framework application without much trouble. Blazor Runtime Size A criticism often leveled at WebAssembly is the size of the download required to run the application and how it makes the app slow. While it is true the debug-mode runtime is around 20MB, the release version is closer to 7MB. It is important to remember this is a one-time download and will be cached by the browser. It is also important to remember that many mobile apps have an initial download in the hundreds of MB, so a slight delay while the browser downloads a runtime ONCE should be more than tolerable for most users. The Solution Head over to (https://github.com/two-thirty-seven/BlazorWithFramework) to get the repo code. This repo holds a solution that spins up a .Net Framework application and embeds the Blazor application into it. NOTE: At this stage, you will need to be on a Windows machine to run the solution due to it using .Net Framework. BlazorApp With .NET 6, Blazor introduced the idea of components being available to Javascript interop (Use Razor components in JavaScript apps and SPA frameworks | Microsoft Learn) This exposes a new method which is the key to making this all work - RegisterForJavaScript(...) This method allows us to call into the Blazor WASM binaries and render the component out to a specified DOM node. So, if we tell the Blazor app to expose a component, we can then run some Javascript in the Framework app and put that component into the page. :-) If you look at program.cs, you will see two components registered: builder.RootComponents.RegisterForJavaScript(identifier: "counter", javaScriptInitializer: "initializeComponent"); builder.RootComponents.RegisterForJavaScript(identifier: "weather", javaScriptInitializer: "initializeComponent"); There is also a build-task in the BlazorApp.csproj file. This will take the build output from that project and copy the assets into the _framework folder in the FrameworkApp project. Yes, it is confusing, but Blazor WASM apps compile into a _framework folder for some reason. Apart from that, the Blazor app is out-of-the-box from the template. FrameworkApp This project is where the magic happens. Looking at _Layout.cshtml, you will see there is a new Javascript block at the bottom: window.initializeComponent = async (component, parameters) => { const elements = document.querySelectorAll(`[data-component="${component}"]`); elements.forEach(element => { var payload = {}; if (element.innerText !== "") { payload = JSON.parse(element.innerText); } console.log(payload); window.Blazor.rootComponents.add(element, component, payload); }); } This code is actually pretty simple. First, it will load the bootstrapping code blazor.webassembly.js that has been copied into the project from the build step above. Next, it adds the initializeComponent callback function onto the window object and this will be called by the Blazor WASM application when each component is registered on startup. Inside this callback, is where we wire up the components to the DOM. We do this by finding all DOM elements which have the specific data-component attribute. The value of this attribute must match the name given to the component in the RegisterForJavaScript(...) call. It will then iterate over each element that matches and will then attach the matching Blazor component to that DOM node. The result should look something like this: The "Counter" component is Blazor WASM but the surrounding page is straight ASP.NET Fra
The Problem
You have a legacy application running on .Net Framework. It performs reasonably well despite its age, and it is
unlikely to go out of support with Microsoft anytime soon. But with .NET 8 (and now 9) well-established, there are
many improvements in code quality, security, speed and even new language features that you are restricted from using
with .NET Framework.
There are various approaches to "upgrading" a .Net Framework application. One method is the "Strangler Pattern"
whereby you carve off a piece of the legacy codebase, rewrite it, and inject it back into the application somehow.
So, how can we leverage the power of Blazor WASM to take over functionality that is current provided by old technology on .NET Framework?
Blazor WebAssembly
Fundamentally, when you run a Blazor WASM application, it is bootstrapped by a Javascript file which in turn downloads the necessary WASM binaries from the compiled application. In debug, these binaries can be relatively large, but when compiled at release time, the binaries can shrink a lot due to tree-shaking and AOT compilation.
Since the Blazor WASM appears to the browser as like a Javascript application, we should be able to build a Blazor WASM application and then surface it inside the .NET Framework application without much trouble.
Blazor Runtime Size
A criticism often leveled at WebAssembly is the size of the download required to run the application and how it makes the app slow. While it is true the debug-mode runtime is around 20MB, the release version is closer to 7MB. It is important to remember this is a one-time download and will be cached by the browser.
It is also important to remember that many mobile apps have an initial download in the hundreds of MB, so a slight
delay while the browser downloads a runtime ONCE should be more than tolerable for most users.
The Solution
Head over to (https://github.com/two-thirty-seven/BlazorWithFramework) to get the repo code.
This repo holds a solution that spins up a .Net Framework application and embeds the Blazor application into it.
NOTE: At this stage, you will need to be on a Windows machine to run the solution due to it using .Net Framework.
BlazorApp
With .NET 6, Blazor introduced the idea of components being available to Javascript interop (Use Razor components in JavaScript apps and SPA frameworks | Microsoft Learn)
This exposes a new method which is the key to making this all work - RegisterForJavaScript
This method allows us to call into the Blazor WASM binaries and render the component out to a specified DOM node. So, if we tell the Blazor app to expose a component, we can then run some Javascript in the Framework app and put that component into the page. :-)
If you look at program.cs
, you will see two components registered:
builder.RootComponents.RegisterForJavaScript(identifier: "counter", javaScriptInitializer: "initializeComponent");
builder.RootComponents.RegisterForJavaScript(identifier: "weather", javaScriptInitializer: "initializeComponent");
There is also a build-task in the BlazorApp.csproj
file. This will take the build output from that project and copy the assets into the _framework
folder in the FrameworkApp project. Yes, it is confusing, but Blazor WASM apps compile into a _framework
folder for some reason.
Apart from that, the Blazor app is out-of-the-box from the template.
FrameworkApp
This project is where the magic happens. Looking at _Layout.cshtml
, you will see there is a new Javascript block at the bottom:
This code is actually pretty simple.
First, it will load the bootstrapping code blazor.webassembly.js
that has been copied into the project from the build step above.
Next, it adds the initializeComponent
callback function onto the window object and this will be called by the Blazor WASM application when each component is registered on startup.
Inside this callback, is where we wire up the components to the DOM. We do this by finding all DOM elements which have the specific data-component
attribute. The value of this attribute must match the name given to the component in the RegisterForJavaScript
call.
It will then iterate over each element that matches and will then attach the matching Blazor component to that DOM node.
The result should look something like this:
The "Counter" component is Blazor WASM but the surrounding page is straight ASP.NET Framework 4.8.
If we inspect the DOM, we can see nothing untoward, just plain markup:
Passing parameters
Rendering components is great, but what if we need to pass some parameters to the component from MVC?
First off, we can simply add a Parameter to the component in Blazor. In this example, we want to give it the API url to pull data from:
To set this value from MVC, we need to pass the properties as a JSON object to the startup script. We can do this by rendering the JSON into the DOM node and then reading it back during the initializeComponent
callback (see above).
Then if you look at the code added to _Layout.cshtml
below, you can see it parses that JSON string into an object and the hands it over to the component.
The final result is this if we run the Blazor app on its own and use the default sample data:
But if we embed it and pass a different URL, we get a different data result:
Developing Blazor components
You will want to develop and test your Blazor components without having to spin the .Net Framework app.
If you look at the program.cs
in the BlazorApp, you will see logic which determine the run state of the project.
if (builder.HostEnvironment.IsEnvironment("Development"))
{
builder.RootComponents.Add("#app");
builder.RootComponents.Add("head::after");
}
else
{
builder.RootComponents.RegisterForJavaScript(identifier: "counter", javaScriptInitializer: "initializeComponent");
builder.RootComponents.RegisterForJavaScript(identifier: "weather", javaScriptInitializer: "initializeComponent");
}
If the ASPNETCORE_ENVIRONMENT
launch setting is Development
, it will run as a normal Blazor standalone WebAssembly project. You can then host the components in the Blazor pages and test away.
If the ASPNETCORE_ENVIRONMENT
launch setting is anything else, it will register the components for JavaScript.
Summary
The ability to embed Blazor WASM components into legacy apps allows you to add modern development tooling with a relatively low friction approach.
What's Your Reaction?