Using blazor components outside of blazor app (eg. knockoutjs)

Viewed 627

I am working on a migration project. I am the only JS Developer on the team, and rest everyone are C# developers. We are looking to migrate our existing application written in .Net MVC Knockoutjs. Since most of out team is C# developers we are giving blazor a try.

Since the entire application rewrite is going to take time, we are hoping to rewrite the application piece by piece in form of blazor components and export those components one at a time to be reused in the existing knockoutjs application.

This will allow us to move over from knockout to blazor whilst continuing to support and maintain existing application.

I have looked at angular elements as one way, however, like I mentioned we want to give blazor a shot. I am curious as to if we could export blazor components into other applications and allow flow of data between them.

Any help/suggestions are appreciated.

Thanks

3 Answers

If you'd like to attempt replacing all the javascript on an MVC page with a blazor component one at a time, you might try this process, it's worked for me.

Firstly, update or convert your MVC project to .Net 5.0.402. It MAY work on an earlier version but I haven't tested it.

To your MVC solution, add a blazor web assembly app project. This is the web assembly components project.

On the MVC view, at the place you wish to render your blazor component add:

@using myawesomewasmprojectnamespace.Pages

<component type="typeof(Counter)" render-mode="WebAssemblyPrerendered"/>

<script src="_framework/blazor.webassembly.js"></script>

The '@using' declaration refers to the blazor web assembly app project, Pages directory. The 'typeof(Counter)' type refers to the Counter.razor component in the default blazor web assembly app project, visual studio supplies.

I'm assuming you'll be able to write your own blazor component to swap with the Counter.razor component.

In the MVC project's _Layout.cshtml, or wherever the <head> tag of the MVC view is, include in the <head> tag for every page with a blazor component in it:

<base href="/"/>

Add the 'Microsoft.AspNetCore.Components.WebAssembly.Server' package to your MVC project.

Add a reference to the blazor web assembly app project, in your MVC Dependencies, project references.

In the MVC apps Startup.cs, 'public void Configure(IApplicationBuilder app, IWebHostEnvironment env)' method, add the following:

app.UseBlazorFrameworkFiles();

In the blazor web assembly app project, Program.cs file, comment out the following line to stop the application looking for a '<div id="app"></div>'

//builder.RootComponents.Add<App>("#app");

Finally, delete the favicon from the blazor web assembly app project's wwwroot directory as it will clash with the MVC one.

This then adds the 'Counter' component to your MVC view.

To add a different component to a different view, insert it with:

@using myawesomewasmprojectnamespace.Pages

<component type="typeof(Myawsomecomponentnamecompletelydifferentfromanymvccontrolleroractionname)" render-mode="WebAssemblyPrerendered"/>

<script src="_framework/blazor.webassembly.js"></script>

And start your blazor component with:

@page "/myawsomecomponentnamecompletelydifferentfromanymvccontrolleroractionname"

This took me ages to put together, please let me know if it doesn't work for you.

After doing a lot of research and reading through number of microsoft docs, I was able to run an individual blazor component outside of blazor in a different framework (knockout mvc in my case),

To start with - I initialized a new Blazor Webassmebly Project in Visual Studio targeting .net 5.0.

For simplicity sake lets take the counter component from the blazor bootstrap application.

Step - 1 Import the component you want to export as reusable component like this- enter image description here

Step 2 - Modify your MainLayout.razor to load the component

enter image description here

Step 3 - This is crucial step. In your Counter component you should add following lines of code, here we are going to set the reference of the component using DotNetObjectReference

inject @inherits LayoutComponentBase @inject IJSRuntime js

In your component code section -

 private int currentCount = 0;

    private DotNetObjectReference<Counter> jsToDotNetBridgeReference;
    protected override async Task OnInitializedAsync()
    {
        jsToDotNetBridgeReference = DotNetObjectReference.Create(this);
        await js.InvokeVoidAsync("SetDotNetReference", jsToDotNetBridgeReference);
    }


    [JSInvokable]
    private void IncrementCount()
    {
        currentCount++;
    }

[JSInvokable] Identifier helps the dotnet to recognize this function as something that be invoked from JavaScript.

Step - 4 Modify the Program.cs and index.html

Modify your index.html and change your div id to counter enter image description here

Next modify your program.cs to look for counter id in html. enter image description here

Step 5 - Set dotnetreference to javascript window object

In your index.html under script tag add the following script -

window.SetDotNetReference = function (pDotNetReference) {
        DotNetReference = pDotNetReference;
    }

That's all you have to do on the blazor webassembly app.

Step 6 - Publish your blazor application to a folder. I use default location for now. Once published you will see following files and folders, and we are interested in _framework folder for most part.

enter image description here

Once publish is done, copy the _framework folder to the root of your other application like so - enter image description here

Step 7 - Go to your desired framework, for now I am gonna use knockout mvc application.

In your layout.cshtml file add reference to the blazor library like so

<script src="~/_framework/blazor.webassembly.js" autostart="false"></script>

Keeping autostart = false , is a great way to make sure your blazor component does not load when its not required.

Step 8 - Create BlazorInterop.js

In your framework project create a js file, I like to name it as BlazorInterop.js (could be anything ) and then reference it in your layout.cshtml like so

<script src="~/BlazorInterop.js?" type="text/javascript"></script>

Inside BlazorInterop.js you will write functions that you want to execute from your host application inside your blazor app. Like this -

       IncrementCounter = function () {
    Blazor.start().then(()=> {
        DotNetReference.invokeMethodAsync('IncrementCounter');
    })
   
}

Step 9 - Create a div in layput.cshtml with id=counter

Create a div with id=counter, this will help blazor appp to recognize the div where it should plant the compoenent like so -

 <div id="counter"> 

    </div>
    <button type="button" onClick="IncrementCounter"> Increment Counter </button>

In order to see the blazor component right from the time page loads set autostart=true else if you want to load the component on demand I have added a button in host app to show that you can just call blazor functions if you want once the component is loaded.

In your blazor interop.js Blazor.start will start the blazor application and then DotNetReference.invokeMethodAsync will trigger the function inside blazor and counter should increment.

Step 10 - Add mime types in your mvc application in your web.config to serve static content like so -

 <staticContent>
      <remove fileExtension=".wasm"/>
      <mimeMap fileExtension=".wasm" mimeType="application/wasm"/>
      <remove fileExtension=".dat"/>
      <mimeMap fileExtension=".dat" mimeType="application/dat"/>
      <remove fileExtension=".br"/>
      <mimeMap fileExtension=".br" mimeType="application/br"/>
      <remove fileExtension=".gz"/>
      <mimeMap fileExtension=".gz" mimeType="application/gz"/>
      <remove fileExtension=".blat"/>
      <mimeMap fileExtension=".blat" mimeType="application/blat"/>
      <remove fileExtension=".pdb"/>
      <mimeMap fileExtension=".pdb" mimeType="application/pdb"/>
      <remove fileExtension=".dll"/>
      <mimeMap fileExtension=".dll" mimeType="application/octet-stream"/>
      <remove fileExtension=".json"/>
      <mimeMap fileExtension=".json" mimeType="application/json"/>
    </staticContent>

And done ! Fire up you knockout application and you will be able to see the component.

This is basically what needs to happen for us to load blazor component in mvc knockout app, This methods should work for any other frameworks too however I am not sure for to add mime types to serve static content in lets say frameworks like angulr, react etc, but I am sure there must be some way. I have not researched on that.

There might be some errors one may run into and so I am leaving some documentation that I referred throughout this process.

Call .Net Methods from JS https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript?view=aspnetcore-6.0

Call JS Methods from .net

https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-6.0

How to correctly use blazor.start

https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/startup?view=aspnetcore-6.0

This took a lot of time for me to put together, this specific workflow has worked for us and is scheduled for production release end of the month.

This has helped us to develop new features for our application in a Blazor, resulting into moving ahead into a new technology without having to get new resources.

One possibility, available from .NET 6 on, is to make the Blazor component(s) available for instantiation from JavaScript by calling JSComponentConfigurationExtensions.RegisterForJavaScript.

I'm also in a situation where I want to migrate an existing JS application to Blazor step by step. However, it is an Angular application. To evaluate a possible solution, I created a demo migration project. See my BlazorInAngularDemo github project for the code and description and a working demo. But as already said, its specifically for Angular. But maybe some ideas, especially in the README file, can help you anyway.

Related