ASP.NET Core Blazor WebAssembly native dependencies

Note

This isn't the latest version of this article. For the current release, see the .NET 8 version of this article.

Important

This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

For the current release, see the .NET 8 version of this article.

Blazor WebAssembly apps can use native dependencies built to run on WebAssembly. You can statically link native dependencies into the .NET WebAssembly runtime using the .NET WebAssembly build tools, the same tools used to ahead-of-time (AOT) compile a Blazor app to WebAssembly and to relink the runtime to remove unused features.

This article only applies to Blazor WebAssembly.

.NET WebAssembly build tools

The .NET WebAssembly build tools are based on Emscripten, a compiler toolchain for the web platform. For more information on the build tools, including installation, see ASP.NET Core Blazor WebAssembly build tools and ahead-of-time (AOT) compilation.

Add native dependencies to a Blazor WebAssembly app by adding NativeFileReference items in the app's project file. When the project is built, each NativeFileReference is passed to Emscripten by the .NET WebAssembly build tools so that they are compiled and linked into the runtime. Next, p/invoke into the native code from the app's .NET code.

Generally, any portable native code can be used as a native dependency with Blazor WebAssembly. You can add native dependencies to C/C++ code or code previously compiled using Emscripten:

  • Object files (.o)
  • Archive files (.a)
  • Bitcode (.bc)
  • Standalone WebAssembly modules (.wasm)

Prebuilt dependencies typically must be built using the same version of Emscripten used to build the .NET WebAssembly runtime.

Note

For Mono/WebAssembly MSBuild properties and targets, see WasmApp.targets (dotnet/runtime GitHub repository). Official documentation for common MSBuild properties is planned per Document blazor msbuild configuration options (dotnet/docs #27395).

Use native code

Add a simple native C function to a Blazor WebAssembly app:

  1. Create a new Blazor WebAssembly project.

  2. Add a Test.c file to the project.

  3. Add a C function for computing factorials.

    Test.c:

    int fact(int n)
    {
        if (n == 0) return 1;
        return n * fact(n - 1);
    }
    
  4. Add a NativeFileReference for Test.c in the app's project file:

    <ItemGroup>
      <NativeFileReference Include="Test.c" />
    </ItemGroup>
    
  5. In a Razor component, add a DllImportAttribute for the fact function in the generated Test library and call the fact method from .NET code in the component.

    Pages/NativeCTest.razor:

    @page "/native-c-test"
    @using System.Runtime.InteropServices
    
    <PageTitle>Native C</PageTitle>
    
    <h1>Native C Test</h1>
    
    <p>
        @@fact(3) result: @fact(3)
    </p>
    
    @code {
        [DllImport("Test")]
        static extern int fact(int n);
    }
    

When you build the app with the .NET WebAssembly build tools installed, the native C code is compiled and linked into the .NET WebAssembly runtime (dotnet.wasm). After the app is built, run the app to see the rendered factorial value.

C++ managed method callbacks

Label managed methods that are passed to C++ with the [UnmanagedCallersOnly] attribute.

The method marked with the [UnmanagedCallersOnly] attribute must be static. To call an instance method in a Razor component, pass a GCHandle for the instance to C++ and then pass it back to native. Alternatively, use some other method to identify the instance of the component.

The method marked with [DllImport] must use a C# 9.0 function pointer rather than a delegate type for the callback argument.

Note

For C# function pointer types in [DllImport] methods, use IntPtr in the method signature on the managed side instead of delegate *unmanaged<int, void>. For more information, see [WASM] callback from native code to .NET: Parsing function pointer types in signatures is not supported (dotnet/runtime #56145).

Package native dependencies in a NuGet package

NuGet packages can contain native dependencies for use on WebAssembly. These libraries and their native functionality are then available to any Blazor WebAssembly app. The files for the native dependencies should be built for WebAssembly and packaged in the browser-wasm architecture-specific folder. WebAssembly-specific dependencies aren't referenced automatically and must be referenced manually as NativeFileReferences. Package authors can choose to add the native references by including a .props file in the package with the references.

SkiaSharp example library use

SkiaSharp is a cross-platform 2D graphics library for .NET based on the native Skia graphics library with support for Blazor WebAssembly.

To use SkiaSharp in a Blazor WebAssembly app:

  1. Add a package reference to the SkiaSharp.Views.Blazor package in a Blazor WebAssembly project. Use Visual Studio's process for adding packages to an app (Manage NuGet Packages with Include prerelease selected) or execute the dotnet add package command in a command shell:

    dotnet add package –-prerelease SkiaSharp.Views.Blazor
    

    Note

    For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

  2. Add a SKCanvasView component to the app with the following:

    • SkiaSharp and SkiaSharp.Views.Blazor namespaces.
    • Logic to draw in the SkiaSharp Canvas View component (SKCanvasView).

    Pages/NativeDependencyExample.razor:

    @page "/native-dependency-example"
    @using SkiaSharp
    @using SkiaSharp.Views.Blazor
    
    <PageTitle>Native dependency</PageTitle>
    
    <h1>Native dependency example with SkiaSharp</h1>
    
    <SKCanvasView OnPaintSurface="OnPaintSurface" />
    
    @code {
        private void OnPaintSurface(SKPaintSurfaceEventArgs e)
        {
            var canvas = e.Surface.Canvas;
    
            canvas.Clear(SKColors.White);
    
            using var paint = new SKPaint
            {
                Color = SKColors.Black,
                IsAntialias = true,
                TextSize = 24
            };
    
            canvas.DrawText("SkiaSharp", 0, 24, paint);
        }
    }
    
  3. Build the app, which might take several minutes. Run the app and navigate to the NativeDependencyExample component at /native-dependency-example.

Additional resources

.NET WebAssembly build tools