Using three.js with ASP.NET Core Blazor Server application

Spread the love

If you know C# and you want to create a cool modern web application… But you are too lazy have no desire or have no ability to learn any front-end things like JavaScript and JavaScript-based frameworks… Then probably the best choice for you is a new Microsoft framework called Blazor.

As the official documentation says Blazor was developed for “Create rich interactive UIs using C# instead of JavaScript”. And it sounds actually cool. No more JavaScript, no more “spaghetti code”, no more callbacks and other scary things 🙂

But, wait. What about 2D or 3D graphics like canvas or WebGL? Does Blazor support them? Hmmm… It seems like we still have to use some JavaScript libraries similar to three.js or Babylon.js.

So, it’s quite amusing subject to investigate. Let’s try.

Creating a Blazor server project

This time I’m using the .Net Core CLI to create the Blazor server project and Visual Studio Code as an IDE.

dotnet new blazorserver

But you can also create a new project with Microsoft Visual Studio.

To check if everything is OK, use the next commands in your command line

dotnet build
dotnet run

Big cleanup

After the creation project has some unnecessary files, so, it’s time to clean up. The image below shows all files and directories to be deleted.

Because the deleted files declared namespaces and service,  the next strings have to be removed from the Startup.cs

...
using Blazor3D.Data;
…
services.AddSingleton<WeatherForecastService>();

Also, we have to get rid of the unused menu items in the NavMenu.razor component. There can be only one 🙂

...
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-bell" aria-hidden="true"></span> three.js
            </NavLink>
        </li>
        @* remove other items *@
    </ul>
</div>
...

And finally, the modified Index.razor should look like below

@page "/"

<h1>Hello, three.js!</h1>

After this step completed the application has pretty simple interface

Adding JavaScript and 3D assets

In this project, I’m going to use a slightly modified example from official three.js documentation. I already have prepared JavaScript code and 3D assets, so I just have to put all files into the wwwroot directory. You can download them here.

The models directory contains an animated 3D model in Collada format and texture image.

The js directory contains three.js library source files (production and debug versions) and helper modules. 

All functions which I’m going to call from my C# code in the project are located in the ThreeJSFunction.js file. Let’s review it briefly.

...
var isPlayed = true; // animation state
...

// loads and setups 3D scene, lights, models, etc.
function loadScene() {
    ...
    // all 3D magic is done here!
}

// plays animation
function onStart() {
    isPlayed = true;
}

//stops animations
function onStop() {
    isPlayed = false;
}

// Module object which encapsulates necessary functions
window.ThreeJSFunctions = {
    load: () => { loadScene(); },
    stop: () => { onStop(); },
    start: () => { onStart(); },
};

// loads scene when page loading complete
window.onload = loadScene;

The code from the official three.js example is mostly located in the loadScene() function. I slightly modified it to control the animation state.

This code is not perfect, I know. With this approach, all objects in the scene are recreated and reloaded every time when we call loadScene() function. But I didn’t want to spend a lot of time with this and, as every lazy human being, I stopped the development on stage “looks like it works… somehow” :).

The onStart() function sets the model’s animation mode as playing, and onStop() function sets mode as stopped.

All three functions are encapsulated in the object which is assigned to the ThreeJSFunctions property of the global window object.

So, it’s time to compose all parts together.

Calling JavaScript code from C#

The Blazor framework has integrated features to call JavaScript code from components C# code. Let’s use it. The modified source code of Index.razor looks like below

@page "/"

@inject IJSRuntime JSRuntime

<div id="threejscontainer" style="width: 300px; height: 300px;"></div>
<div>
<button type="button" class="btn btn-primary" @onclick="Stop">
    Stop
</button>

<button type="button" class="btn btn-primary" @onclick="Dance">
    Dance
</button>
</div>

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
      await JSRuntime.InvokeVoidAsync("ThreeJSFunctions.load");
    }

    public async Task Stop()
    {
      await JSRuntime.InvokeVoidAsync("ThreeJSFunctions.stop");
    }

    public async Task Dance()
    {
      await JSRuntime.InvokeVoidAsync("ThreeJSFunctions.start");
    }
}

The @inject IJSRuntime JSRuntime string adds an IJSRuntime dependency to our component. This dependency contains all functionality we need to call JavaScript code.

The <div id=”threejscontainer”…> markup is just a placeholder for three.js 3D canvas. All 3D stuff will be displayed there. 

Two buttons below should help with starting and stopping the model’s animation. Their onclick events are handled by the corresponding methods where JSRuntime.InvokeVoidAsync() is called. The name of the appropriate JavaScript function is passed as an argument.

And finally, the overridden OnAfterRenderAsync() handler of the component calls ThreeJSFunctions.load method where the entire three.js scene is loading.

Final touches

Before executing the application yet another thing has to be done. We have to add our JavaScript code as the <script> reference to the _Host.cshtml file. It’s a good practice to add all scripts at the end of the file before the closing </body> tag.

...
    <script src="_framework/blazor.server.js"></script>
    <script type="module" src="~/js/ThreeJSFunctions.js"></script>
</body>
</html>

So, let’s try what we have got.

Hmmm… Looks good, but where is the animated model? Let’s press F12 in the browser and see the console messages.

Yeah. That’s the reason. The files with .dae extension are not allowed MIME types in our application.

Let’s correct this.

Adding a new MIME-type

The recommended way to add a new MIME type is using FileExtensionContentTypeProvider. So, let’s use it in the Configure() method of the Startup.cs.

...
using Microsoft.AspNetCore.StaticFiles;
…

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
          ...
            app.UseHttpsRedirection();

            var provider = new FileExtensionContentTypeProvider();
            provider.Mappings.Add(".dae", "text/xml");

            app.UseStaticFiles(new StaticFileOptions
            {
                ContentTypeProvider = provider
            });
	...
        }
...

Now it should work.

And YES! It works!

But another console error appeared. This time something is wrong with Blazor framework script.

Every programmer must be a perfectionist. Even if he is a lazy human being. So, the bug should be fixed.

Struggling with bugs

As googling shows, it’s a known issue when FileExtensionContentTypeProvider is used in the Blazor application.

To fix it, just call yet another “empty” app.UseStaticFiles(); after your configurations.

...
 app.UseStaticFiles(new StaticFileOptions
            {
                ContentTypeProvider = provider
            });
app.UseStaticFiles();
...

And, that’s it.

You can find complete source code in my Blazor3D repository on Github.

Similar projects

If you prefer Babylon.js, you can take a look at BlazorBabylonJs project.

Also, you can subscribe to Alice Lab channel on Youtube where you can find a lot of videos about using WebGL inside the Blazor.

That’s all and happy coding!