DEV Community

Fouad sa
Fouad sa

Posted on

Your .Net Blazor Skills Just Became Blockchain Skills

I've been writing C# for years. Enterprise systems, CRMs, the kind of code that runs quietly in the background and pays the bills. When I started getting curious about blockchain, I assumed I'd have to leave all of that behind and start over in JavaScript, Rust, or Solidity.

It turns out I didn't. On the Internet Computer (ICP), a whole web app, frontend included, can be served straight from the blockchain. And Blazor WebAssembly compiles to exactly the kind of static files ICP's asset canisters know how to serve.

So I tried it. It worked in the end, but the road there was full of potholes nobody had documented. This article is the map I wish I'd had, plus a template that lets you skip straight to the fun part.

Why .NET developers deserve a starting point

If you open the ICP tooling today and look for a template, you'll find options for React, Svelte, Vue, and plain JavaScript. For C# developers, there was nothing.

So I built one. As far as I can find, this is the first Blazor template for icp-cli: one command gives you a Blazor WebAssembly frontend and a Motoko backend, deployed together on the Internet Computer.

To be fair, I'm not the first person to run Blazor on ICP. Michiel Post's Dfinity.Blazor library and the ICP.NET agent showed years ago that C# and ICP can work together, and my template stands on their shoulders.

What was missing was a ready-made starting point for today's tooling: .NET 10, icp-cli, and the current Motoko build pipeline. That's the gap this template fills.

That matters more than it sounds. There are millions of .NET developers out there, and most of them will never try a new platform if the first hour is spent fighting configuration. A missing template quietly tells them: this place isn't for you.

I didn't want to accept that. Blazor already gives us a component model, strong typing, and a single language across the stack. ICP gives us hosting with no servers, no cloud bills in the usual sense, and tamper-proof code. Putting the two together felt natural. It just needed someone to clear the path.

What's in the template

The template gives you a working full-stack app in a few minutes:

  • Frontend: Blazor WebAssembly on .NET 10, served from an ICP asset canister.
  • Backend: a small Motoko canister with a hello function.
  • Glue: the ICP JavaScript agent, bundled so Blazor can call the backend through JS interop.
  • Tooling: icp-cli with icp.yaml configuration, and Motoko compiled through mops.

You type a name, click a button, and your C# frontend calls a canister on the blockchain and shows Hello, ICP!. Small, yes. But every piece of the pipeline is real, and that's what you build on.

To create a project, you only need icp-cli and the .NET 10 SDK installed:

icp new my-app --git https://github.com/mitnick2012/icp-cli-templates --subfolder blazor-hello-world
cd my-app
icp network start -d
icp deploy
Enter fullscreen mode Exit fullscreen mode

Open the frontend URL that icp deploy prints, and you're running a Blazor app on a local ICP network. The same project deploys to mainnet when you're ready.

The bug that almost made me quit

My first version looked perfect. The page loaded, the input box was there, the button was there. I clicked the button and nothing happened. No error in the browser console. No exception in Blazor. Just silence.

This is the worst kind of bug, because there's nothing to search for.

Here's what was going on. I loaded the ICP agent the modern way, as an ES module:

<script type="module" src="icp-agent.js"></script>
Enter fullscreen mode Exit fullscreen mode

Browsers treat module scripts as deferred and load them on their own schedule. Blazor, meanwhile, boots its runtime and starts wiring up components. Sometimes Blazor won the race. When it tried its first JS interop call, the agent wasn't there yet, and the button's event handling broke without a word.

The fix was to stop fighting the browser and use the oldest, most predictable tool available. I bundled the agent with webpack into a single plain JavaScript file that exposes one global, window.IcpAgent, and loaded it with defer:

<script src="js/icp-agent.bundle.js" defer></script>
<script src="_framework/blazor.webassembly.js"></script>
Enter fullscreen mode Exit fullscreen mode

Classic scripts with defer run in the order they appear, before DOMContentLoaded. So by the time Blazor makes its first interop call, window.IcpAgent is guaranteed to exist. On the C# side, calling the backend becomes ordinary Blazor code:

@inject IJSRuntime JS

<input @bind="name" />
<button @onclick="Greet">Greet</button>
<p>@greeting</p>

@code {
    private string name = "ICP";
    private string greeting = "";

    private async Task Greet()
    {
        greeting = await JS.InvokeAsync<string>("IcpAgent.hello", name);
    }
}
Enter fullscreen mode Exit fullscreen mode

It's not the most fashionable solution. But it works every single time, and I'll take boring and reliable over clever and flaky. The pattern is modeled on the community Dfinity.Blazor library, which had already proven it in practice.

Two more things I learned the hard way

Don't hardcode canister IDs. Your backend canister gets one ID on your local network and a different one on mainnet. Hardcoding it means editing code every time you switch, and eventually shipping the wrong one. The template reads the canister ID at runtime from the ic_env cookie that the asset canister provides. Deploy anywhere and the frontend finds its backend on its own.

Bundle size is not the problem you think it is. My first worry was that bundling the agent would bloat the app. The webpack bundle is about 271 KB uncompressed. That sounds like a lot until you remember the Blazor WebAssembly runtime itself is over 5 MB. With Brotli compression from the asset canister, the bundle drops to roughly 80 KB on the wire. It's not worth losing sleep over.

All of these gotchas are documented in the template's README. If you hit something new, please open an issue so the next person doesn't have to rediscover it.

A first for the ICP community

When I shared the template with DFINITY, the team told me it was the first time someone from the community had contributed a template. They're planning a dedicated section in their templates repository for community-contributed templates, so projects like this one have an official place to be found.

That's the outcome I'm proudest of. The door is now open for developers to bring their own stacks to ICP, and .NET got there first.

What's next: C# all the way down

Blazor on the frontend is only half the story. The backend in this template is still Motoko.

I'm now working on running C# itself inside an ICP canister, compiled to WebAssembly. A hello-world C# canister already runs on my local network. The goal is a proper developer kit for building .NET apps that run inside ICP.

If that works out, a .NET developer could build a full on-chain app, frontend and backend, without writing a line of anything but C#. That's the article I really want to write.

Try it, break it, tell me

If you're a .NET developer who has been curious about Web3 but didn't know where to start, this is your starting point. It takes one command and a few minutes.

Template: mitnick2012/icp-cli-templates/blazor-hello-world

Star the repo if it helps you, open an issue if something breaks, and send a pull request if you find a better way. And if you build something with it, I'd genuinely love to see it.


Fouad Sa is a senior .NET developer exploring how to bridge Web2 and Web3.

Top comments (0)

⌂ 换网址