Runninng C# In A Browser With WASM

c# online compiler app image

About

This post is a quick overview of the C# Online Compiler I made some time ago. It enables you to run C# in the browser. This way, your code won’t be sent to a backend server to be compiled. This is achieved by utilizing Blazor WASM(WebAssembly), which allows us to make and run C# apps(including the Roslyn compiler APIs) in a browser. 

I was going to make this a much more in-depth blog post about compiling C# in C#, working with the ASTs(abstract syntax tree), dynamically loading assemblies and NuGet packages, … But since it’s 2026 and we are all using AI these days, not many people will see this anyway. So I’ll just make this a quick overview post of my project.

If you are interested in the specifics, refer to the code and some of the links I’ll proveide blow. You can find the full code in the GitHub code repository here.

Here’s the app embedded via iframe if you want to test it out. Full screen version here.

Compiling C# in C#

A very cool thing you can do is programatically compile a C# string into an executable assembly DLL within C# itself. This can be done using the Royslyn Compiler APIs. These are available in the Microsoft.CodeAnalysis namespace.
1. First thing you do is to create a syntax tree from the string CSharpSyntaxTree.ParseText().

2. Then you can compile it with CSharpCompilation.Create(), which will create a compilation object. Here, you will also have to add references to DLLs that the code you are compiling depends on. This could be another DLL you made, NuGet packages, or your standard dependencies like System.*

Note: This is where the difference is between running this in a browser or on a server/desktop app.

For server/desktop, you would use AppDomain.CurrentDomain.GetAssemblies() to get the assembly references.

But if you are using Blazor like I am, you will use an HTTP client to get the _framework/blazor.boot.json file. Then you will use it to get out the URLs of your dependencies, download them, and make MetadataReferences that you can later use.

3. Finally, you can call GetDiagnostics() on the object to get any errors or warnings, or you can call Emit(), which will output a stream or byte array of the compiled DLL.

Dynamic Assembly Loading with AssemblyLoadContext

Now that we compiled our DLL, we need to load it and the assemblies it depends on(if not loaded already). Then we can use Reflection to get the method we want to call and invoke it.

The old way to do it was by using AppDomain and registering a custom dependency resolver while loading your assembly with Assembly.Load(assemblyByteArray).
At the end, I decided to use this because I just couldn’t get the output of the Console.WriteLine() working using the new way described below.

Ideally, you should be using the new way by making a plugin class that inherits from AssemblyLoadContext. In your custom plugin, you can then override the Load() method. I wasn’t planning on using any unmanaged dlls so that method just returns an empty pointer.

Note: If you keep creating and loading new assemblies, you will cause a memory leak. The assemblies are “scoped” to the process, not the thread. So even if the task/thread you loaded the assembly in is disposed of, the assembly will still stay loaded in memory

You should consider using collectible assemblies that can be unloaded. Checkout the documentation here. Additionally, if you are using the same assembly multiple times, you should consider caching it and reusing it instead of unloading and loading it repeatedly.

Adding Nuget Packages

If you want to add NuGet Packages programmatically to your code, you would ideally just use the NuGet Client SDK NuGet package provided by Microsoft. I found these two very useful posts, here: post 1post 2, showing you how to do it.

However, in the end, it turned out I couldn’t use it as this library because it looks for the NuGet package source URL inside the NuGet.Config XML file on the file system. Since this is a web app, the path is never found, and you get an exception.

I would be great if you could just specify the URL in code as an option instead of the library looking for it in a file on the file system(maybe there is a way to do it, but I didn’t find it, if anyone knows how, let me know in the comments). 

So I had to do it the hard way and implement all the logic and HTTP REST API calls by myself.

You need to call these API so query NuGet for the packages. We are doing the same thing you would be manually in the UI, just programmatically via an API.
A NuGet package(.nupkg) is just a zip file with the following contents. If you go inside the lib you will find the DLLs for different TFMs(target framework moniker). See more about TFMs here. We need to select the best match for our case and then load the DLL just like we did in the beginning with InitailizeMetadataReferences() function.
Now we have to recursively get all the dependencies for the NuGet package we added and repeat the process for each of the dependencies, and their dependencies and the dependencies of the dependencies, … until you get to the bottom.
Finally, we need to add all the packages. I made an additional function called addLateReference() that takes the package DLLs and their dependencies DLLs and tries to add them to the metadataReferences list if the packages don’t exist already.
Now you should be able to reference these packages in your code using the using statements.

Language Server

I wanted to add an LSP(language server protocol). This is what gives your IDE the information for the autocompletion suggestions and information about a class, method, … Here’s the link to a C# LSP if you are interested. I, however, decided to skip implementing this as the project was already growing in scope and I wasn’t sure if I could run the aforementioned C# LSP process directly in the browser.

After you get the data from the language server, you can use the code editor APIs(in my case, the Monaco editor I’ll talk about later on) to show the suggestions to the user.

lsp-flow-diagram
Source: https://learn.microsoft.com/en-us/visualstudio/extensibility/media/lsp-flow-diagram.png?view=vs-2017&viewFallbackFrom=vs-2022

Debugger

Another thing I was considering adding was a debugger. Since .NET was “open-sourced” a while ago, I was expecting I could easily get a C# debugger, and in fact, at some point, a NuGet package for this was available before Microsoft decided to take it down because they had apparently “made it available by mistake”.

Here’s a GitHub issue that’s been open for almost a decade at this point: “Kindly reconsider the licensing for .NET Core debugging libraries #505“. And here’s a post from JetBrains about the issue: “Rider EAP 17: NuGet, unit testing, good and bad news on debugging“. 

So yeah… no debugger for us because Microsoft is being Microsoft again.

Monaco Editor

For the code/text editor if have used the Monaco editor, which is a free and open source code editor from Microsoft. It’s actually the code editor used inside VS Code. You can embed it into your web project, like I have done here and in quite a few other projects as well(GDS ViewerAsmIDEDecent PasteDecent DiffCode Diagram Generator).

It offers many features like syntax highlighting, IntelliSense/autocomplete, code folding, multi-cursor editing, find and replace, diff editor support, bracket matching, parameter hints, hover tooltips, error markers, code formatting, minimap, split view, custom themes, language services, custom language definitions, etc.

GUN.js Decentralized Database

At a later point, I thought I could add a login and the ability to save the editor contents. I was already playing around with GUN.js decentralized database for some other project, so I thought I could use it here too.

As it supports events/data streaming, I added the “live share” feature that lets you share your editor with other people and live edit the code together.

Read more about it in this post here.

Leave a Reply

Your email address will not be published. Required fields are marked *

The following GDPR rules must be read and accepted:
This form collects your name, email and content so that we can keep track of the comments placed on the website. For more info check our privacy policy where you will get more info on where, how and why we store your data.