Use DotNet in AL – Even in the cloud!

In this video, I show how to use DotNet in AL, even in the cloud version of Microsoft Dynamics 365 Business Central without using Azure Functions or other 3rd party hosting methods.

https://youtu.be/Uyxoc-xgJeY

As usual, the source code can be found on my Github.


In this video, Erik demonstrates a creative technique for running .NET code directly inside Business Central’s cloud environment — something Microsoft intentionally restricted for security reasons. By leveraging Blazor’s WebAssembly-based .NET runtime and embedding it within a control add-in, Erik shows how you can execute C# code right in the browser, without needing external Azure Functions or web services.

The History of .NET in Business Central

The history of Business Central (formerly NAV) and .NET goes a long way back. DotNet used to be the secret sauce in the developer’s toolbox — if there was something you couldn’t accomplish within C/AL or AL, you could turn to .NET to get it done.

When Business Central moved to the cloud, Microsoft cut off access to .NET from a totally valid security perspective. Business Central runs as a multi-tenant installation, and through .NET reflection and similar capabilities, developers could potentially break out of the sandbox. Microsoft simply couldn’t have custom .NET code deployed on their servers.

So developers were back to square one — back to pure AL.

Previous Workarounds

There have been a couple of ways to work around this limitation, and they all basically follow the same idea: host your .NET code externally (in an Azure Function, for example) and then call it via web services. In this video, Erik demonstrates a fundamentally different approach — actually running .NET code inside Business Central served from dynamics.com.

The Demo: C# Code Running in the Cloud

The demo starts with a very simple piece of C# code written in Visual Studio 2019. It’s a static class with two functions:

  • TestFunction — takes two integers and adds them together
  • Ping — simply returns “Pong”

One important detail (revealed later in the video) is that these functions need to be marked with the [JSInvokable] attribute so that the JavaScript-based .NET runtime can find and call them from the browser.

In the Business Central client running on dynamics.com, Erik clicks “Ping” and gets “Pong” back. He clicks “Calc 2 + 3” and gets the result 5. The calculation is genuinely performed by .NET code running in the browser.

How It Works: Blazor and WebAssembly

The key enabler is Blazor, a Microsoft technology for building web applications in C#. At the core of Blazor is a version of the .NET runtime written in JavaScript — specifically, the Mono runtime. Mono, which became part of Microsoft’s ecosystem when they acquired Xamarin, is used in many places (like Unity). Microsoft created a JavaScript/WebAssembly version of Mono for Blazor.

Erik’s insight was: why not take that .NET runtime and put it inside a control add-in in Business Central? By hosting the entire runtime within an app, you effectively get a .NET runtime environment running inside the user’s browser.

The AL Code

The Business Central page is straightforward. It’s a navigate page with a user control and two actions in the footer bar:

page 50133 "Test DotNet in AL"
{
    PageType = NavigatePage;
    UsageCategory = Administration;
    ApplicationArea = all;
    layout
    {
        area(Content)
        {
            usercontrol(Test; DotNetTest)
            {
                ApplicationArea = all;
                trigger ControlReady()
                begin
                    Ready := true;
                end;

                trigger Result(d: Integer)
                begin
                    Message('The result is %1', d);
                end;

                trigger PingResult(t: Text)
                begin
                    Message(t);
                end;
            }
        }
    }
    actions
    {
        area(Processing)
        {
            action(TestAction1)
            {
                Caption = 'Ping';
                InFooterBar = true;
                ApplicationArea = All;
                trigger OnAction()
                begin
                    if Ready then
                        CurrPage.Test.Ping()
                    else
                        Message('Dotnet still loading....');
                end;
            }
            action(TestAction2)
            {
                Caption = 'Calc 2 + 3';
                InFooterBar = true;
                ApplicationArea = All;
                trigger OnAction()
                begin
                    if Ready then
                        CurrPage.Test.Calc(2, 3)
                    else
                        Message('Dotnet still loading....');
                end;
            }
        }
    }
    var
        Ready: Boolean;
}

The control add-in exposes two procedures — Calc and Ping — and corresponding events. The Calc procedure calls into JavaScript using something like DotNet.invokeMethodAsync('TestLib', 'TestFunction', a, b), while Ping calls DotNet.invokeMethodAsync('TestLib', 'Ping'). When results come back, the JavaScript calls Microsoft.Dynamics.NAV.InvokeExtensibilityMethod to send the result back to AL.

Bundling the .NET Runtime

The control add-in definition includes all the files needed for the .NET runtime to operate. The runtime itself consists of:

  • A JavaScript bootloader (blazor.webassembly.js)
  • A WebAssembly file (the compiled Mono runtime — extremely fast to load and execute)
  • A collection of .NET DLL files (the framework assemblies plus the custom TestLib assembly)

The bootloader contains a manifest that lists the entry assembly (TestLib), all required framework DLLs, and the runtime files. It also includes hash values for each file for integrity verification.

The Tricks: Overcoming Deployment Challenges

Renaming DLLs to PNG

One of the biggest challenges was getting DLL files served through Business Central’s web server. Control add-ins allow you to bundle JavaScript as scripts, but DLL files aren’t a recognized file format for serving. However, the images property of a control add-in accepts any file — AL doesn’t actually validate the content.

The catch is that the web server at dynamics.com needs to know the file type to serve it with the correct HTTP headers, and it’s not interested in serving .dll files. Erik’s solution: rename all DLL files to .png. The web server happily serves PNG files, and the customized bootloader knows to request them with the .png extension.

Fixing Resource Paths

Blazor normally expects to be hosted in a standard way where _framework is a subfolder of the website’s root. Inside a Business Central control add-in, the files are served from a deeply nested, dynamically generated URL — something like:

https://msca1caca-something.appservice.ca.businesscentral.com/resources/extractedresources/<random-number>/dotnet/_framework/...

To resolve this, Erik uses the Business Central JavaScript API function Microsoft.Dynamics.NAV.GetImageResource. Given the name of an image resource in the control add-in, this function returns the actual URL where the file is served. By overriding the fetch command in the bootloader, the .NET engine is redirected to the correct URLs for all its resources.

Updating Assembly Hashes

When the C# code is modified and recompiled, the hash value in the bootloader manifest must be updated to match the new DLL. Erik does this manually — copying the new hash from the freshly compiled output and pasting it into the bootloader configuration. It’s not elegant, but it works.

Updating and Redeploying

To demonstrate the live update workflow, Erik modifies the C# code to change the calculation from a + b to (a + b) * 2, rebuilds the project, copies the new DLL (as a PNG) into the control add-in, updates the hash value in the bootloader, and redeploys to the cloud. After deployment, clicking “Calc 2 + 3” returns 10 — confirming the updated .NET code is running.

Limitations and Considerations

  • .NET Standard only — The code must target .NET Standard. You cannot use APIs that reach outside the browser sandbox (file system access, etc.).
  • Browser sandbox — All execution is confined to the browser’s security sandbox, which is actually a feature from a security perspective.
  • Extension size — The extension includes a complete .NET runtime, making it larger than typical extensions. This can be optimized by removing unused framework assemblies.
  • Deployment time — Deploying to the cloud takes longer due to the extension size.

Is This Actually Useful?

Erik raises the question of whether this is just a clever hack or something genuinely practical. His argument is compelling: users often have powerful desktop machines that are underutilized while browsing Business Central. Being able to leverage the client’s own machine for processing is just as valid as calling out to an Azure Function — and potentially faster since there’s no network round-trip to an external service.

Use cases could include image manipulation, complex calculations, data transformations, or any processing that benefits from C#’s capabilities without requiring server-side resources. The .NET runtime in the browser via WebAssembly is actually quite performant.

Conclusion

By embedding Blazor’s WebAssembly-based .NET runtime inside a Business Central control add-in, Erik demonstrates that it’s possible to run arbitrary C# code in the cloud version of Business Central — entirely within the browser. The technique involves some creative workarounds (renaming DLLs to PNGs, overriding fetch paths, using Business Central’s image resource API), but the end result is a fully functional .NET runtime that opens up possibilities previously thought impossible in the cloud. The source code is available on Erik’s GitHub for anyone who wants to experiment with it.