JT's Weblog

Blazor WASM First Impression: Building a Game with AI and DDD

published: November 18, 2025 estimate: 8 min read view-cnt: 2 views

It’s been a while since my last handmade-post 😆 Well, I think I’ve already given up on making a post on a daily basis Especially since I am having education recall this week PLUS trying to build my VIM-keys and Snake inspired game using Blazor wasm

I will leave the game concept to another article, and do a quick review of what I’ve learned so far
This article is going to cover two major parts, the first is to discuss the developer experience (DX) of Blazor wasm, and the second is to discuss the DX of AI-driven development
I tried putting things I remembered here; from the start of the project to the last commit I’ve made
Things will be written without a specific structure

My .NET Background

I’ve used WebForm, MVC, RazorPages in my working experience, however, I switched to other front-end stacks temporarily since 2023 (e.g. react, astro.js)

.NET 10 just came out last week on 2025-11-10, making it easily my go-to choice for this small side project
Hopefully, this project would remind me about some good old full-stack development memories

Technical Motivation

I want to start by working on the core logic of the game and a thin UI layer without bothering with the backend stuff, so starting with Blazor wasm is a reasonable choice

Also, this is a good opportunity to use domain driven development (DDD) as well where all the layers like UI, API and DB should be replaceable except for the core domain logic

First Impression

My first task when using any starter template is to clean up the stuff I don’t need at the time

So, say goodbye to built-in js, css, API, Layout, sidebar and navigation

The only tricky thing is that this file “wwwroot/index.html” is used in the build process which is not so intuitive IMHO (I am too used to JAMstack stuff 😂)

The issue persists even if you run dotnet watch; you still need to hit ctrl+R to restart the app and apply the changes

JSInterop

AI generated different ways to handle events and javascript interoperation

Since we’re trying to run DDD in this project, the expected way to deal with interop is to minimize the usage of javascript

However, things like registering keydown events to the window object or invoking arbitrary functions of DOM elements are just inevitable at this point

What we can do is add a layer of abstraction, extract commonly used components, and hide interop details from the caller

Minimize The Javascript

Avoid registering events on the top-level objects such as window, document or body if possible

Once I registered the keydown handler at the component level, I was able to eliminate the weird event registration pattern between .NET and JS

Try to declare your event this way whenever possible, Blazor already provides a clean way to delegate events on component markup with rich built-in event arguments support.

You also gain the benefit of omitting “StateHasChanged” when declaring event handlers like this.

It reminds me of React, where I avoid overly declaring useEffect by focusing my implementation on user-triggered events

AI Driven Development

Opinionated Design

The LLM gave a full-fledged game state enumeration: NotStarted, Ready, Playing, Completed

I then simplified to: Ready and Playing (since these two states are sufficient for the current gameplay)

Declaring new models everywhere

This is more of a personal preference, I tend to defer declaring new models to the last minute

I guess a polite explicit prompt would solve this problem

Weird Part

Most of the time, the LLM nails the form validation part in one shot

However, sometimes it populates two different approaches at once without a clue

Not to mention the LLM tries to reimplement those errors if we fix them quietly (sometimes you need a proper shout 🤣)

The Good Part

The best architecture is to create once, and forget it

Parts of the code I’ve never visited after the LLM generated them

These things are surely where the LLM shines (e.g. the core logic of moving a player)

The Bad

The LLM gave eval for some JSInterop tasks, I was amazed by the fact that not only was the code inelegant, but also it didn’t work at all

It wasn’t that we cannot use eval in an InvokeVoidAsync call, but the js pre-defined object arguments were not available at all

Still have no idea why the LLM came up with this solution, but it is a strong indicator that the LLM cannot work with Blazor proficiently yet

A few more instances:

HTML Still Matters

Since all HTML requires a build process in Blazor, it is way more efficient for me to use the data:, protocol in a browser’s address bar

Then, do all the nitty-gritty proof of concepts there

Blazor Weirdness

I found out there are plenty of ways to declare swappable components

For now, <DynamicComponent> is good enough; Maybe one day I will hit its limitation and switch

Scoped styles require additional setup like this <link href="{PACKAGE ID/ASSEMBLY NAME}.styles.css" rel="stylesheet">

It is not a problem for most developers who don’t tamper with the starter template

However, I deleted that line of code on the first day 🤣

I purely solved this problem from reading the docs (Docs FTW!)

Blazor has amazing data binding features, however, it does not support binding to an arbitrary attribute of an element

Part of the reason is that HTML specs are a mess when it comes to “optional attributes”
e.g. checked, required, disabled… etc

HTML specs make it clear how boolean attributes should work, however, it is still confusing for developers to make the right call

Thus, when you try to bind a boolean variable to an optional attribute in Blazor, it renders the attribute with an empty string value (e.g., checked="") instead of removing it or setting it to null/false

This makes the browser read the attribute as true (which is NOT what we want)

My workaround is to add a few more JSInterop calls until it is correct


There was an error in the devtools console:

Firefox can’t establish a connection to the server at wss://localhost:45289/. aspnetcore-browser-refresh.js:350:25

It only happens when I run dotnet watch instead of dotnet run
However, it doesn’t affect the actual DX, so I just leave it there

Lastly, Blazor is kind enough to support arbitrary inline attributes rendering

However, the value is read-only (i.e. immutable), you cannot bind it to a variable

Painful DevOps!

I tried to deploy my website to Azure, however, it doesn’t support .NET 10 at the time I was trying (2025/11/14)

Thankfully, there’s not much work to do to downgrade my newly created project (no need to install additional SDK as well)

I spent almost 4 hours debugging the fingerprint problem in the file wwwroot/index.html

I tried hard using AI to troubleshoot, and ended up finding this GitHub Issue

Sadly, it doesn’t work in my case, so I removed the fingerprint from the code
I will leave this to my future self 🫠

Painful EditForm!

BIG issue with <EditForm>!

I was trying to make a form inside a dialog using all the native stuff

Turns out that getting values from a form element in .NET is not a trivial task

I don’t want to invoke new FormData(form) or form.elements via JSInterop

Thus, <EditForm> seems to be a reasonable option

Background story:

I used an invisible submit button with <form method="dialog"> and <dialog closedby="any"> to achieve three different ways to submit a form

  1. pressing ENTER
  2. hitting ESC
  3. clicking the backdrop of a dialog

However, it breaks the existing logic where users can submit the form via a simple ENTER keystroke

The <EditForm> version doesn’t close the dialog when submitting the form

My workaround is to handle all the submit and close events manually via JSInterop in the .NET code

Which is almost same amount of work to explicitly declaring all the submission event handlers

My Own Handicap

I use LazyVim to develop the Blazor app. There’s no LSP or syntax highlighting available at all

Not to mention debugger or intellisense

Huge handicap in terms of developer experience, however, this is a good way to force myself to use more AI tools

Lastly, Claude Web UI malformats Blazor code snippets, which is also troublesome.

It is solvable by injecting some scripts or styles, but making all these extra efforts is just a bummer

Verdict

After this first week of experience, I found my mental model works like this:

  1. try to improve the code to meet my own taste
  2. find out limitations
  3. implement a workaround
  4. gradually build my own toolbox or framework on top of the existing one

Hopefully, I can keep working on this project until I reach step 4
Stay tuned for more Blazor or game development content, I will see you in the next one 😎



No comments yet

Be the first to comment!