惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
WordPress大学
WordPress大学
博客园 - 三生石上(FineUI控件)
H
Help Net Security
小众软件
小众软件
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
Apple Machine Learning Research
Apple Machine Learning Research
S
SegmentFault 最新的问题
Last Week in AI
Last Week in AI
爱范儿
爱范儿
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
IT之家
IT之家
博客园 - 【当耐特】
V
Visual Studio Blog
大猫的无限游戏
大猫的无限游戏
博客园_首页
Jina AI
Jina AI
D
Docker
博客园 - 司徒正美
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
阮一峰的网络日志
阮一峰的网络日志

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I bypassed Blazor WebAssembly's Virtual DOM using raw...
UnitBuilds · 2026-05-25 · via DEV Community

Table Of Contents

The Bottleneck: When WebAssembly UI gets sluggish

If you've ever built a heavy data grid, a real-time telemetry dashboard, or a line-of-business spreadsheet application in Blazor WebAssembly, you've likely hit the rendering wall.

Blazor WASM is incredibly productive, but when thousands of cells change multiple times a second:

  1. Every property change triggers an event.
  2. Every event triggers standard Blazor component cycle overhead (StateHasChanged).
  3. The Virtual DOM (VDOM) diffs the old rendering tree with the new one.
  4. JS-Interop serializes the diffs and updates the browser DOM.

Under high frequency (like 100+ updates per second on 5,000+ fields), this pipeline stutters. The CPU spikes to 100%, garbage collection (GC) triggers pauses, and key-press latency rises.

We decided to see if we could completely bypass Blazor's rendering pipeline while maintaining C# as the single source of truth for business logic.

Our solution is V.A.L.I.D.—a compile-time state-tracking framework that shares raw WebAssembly memory slabs directly with JavaScript.

Step 1: The Unmanaged Memory Slab in C

To avoid heap allocations and GC overhead when managing object states (dirty, error, busy, deleted), we allocate a contiguous block of native memory on the WebAssembly linear heap.

We built a custom bump allocator wrapper called UnmanagedSlab<T> that uses NativeMemory.Alloc:

public unsafe class UnmanagedSlab<T> : IDisposable where T : unmanaged
{
    private T* _pointer;
    private readonly int _length;

    public UnmanagedSlab(int length)
    {
        _length = length;
        // Allocate contiguous memory in the WASM heap
        _pointer = (T*)NativeMemory.Alloc((nuint)length, (nuint)sizeof(T));
    }

    public ref T this[int index] => ref _pointer[index];

    public void* GetUnsafePointer() => _pointer;

    // Dispose calls NativeMemory.Free...
}

Enter fullscreen mode Exit fullscreen mode

Every business object in the application is registered to a fixed slot in this slab. Its state flags are represented as single bits in a System.UInt128 mask register, giving us up to 128 fields tracked per object in O(1) time with 0 heap allocations.

Step 2: The JSExport Bridge

Next, we need to let JavaScript know where this memory block is. We use .NET 8's new JS interop system with [JSExport] inside our WebWorkerBridge class:

public static partial class WebWorkerBridge
{
    private static UnmanagedSlab<System.UInt128>? _stateSlab;

    [JSExport]
    public static unsafe nint GetStatePointer()
    {
        if (_stateSlab == null) return 0;
        return (nint)_stateSlab.GetUnsafePointer();
    }
}

Enter fullscreen mode Exit fullscreen mode

When our Blazor page initializes, we retrieve this pointer and pass it directly to JS as a long integer address:

var ptr = WebWorkerBridge.GetStatePointer();
var len = WebWorkerBridge.GetSlabLength();
await JSRuntime.InvokeVoidAsync("__VAVID_INITIALIZE_INDUSTRIAL_BYPASS__", (long)ptr, len);

Enter fullscreen mode Exit fullscreen mode

Step 3: Surgical DOM Mutation in JS

Once JavaScript receives the memory address, it connects to Mono's WebAssembly linear heap buffer (HEAPU8 array).

First, we compile a surgical map of DOM inputs. Every VavidInput.razor component renders standard metadata data-attributes rather than binding inputs to Blazor:

<input class="vavid-control"
       value="@Value"
       data-vavid-slab-index="@SlabIndex"
       data-vavid-bit="@BitIndex" />

Enter fullscreen mode Exit fullscreen mode

Our JS code parses these elements on startup and runs a high-performance requestAnimationFrame loop.

Because we know the exact byte offsets, JS can read the dirty/error/busy bitmasks directly from the heap array and surgically toggle class names without going through the VDOM:

function syncSurgicalMap() {
    const h = globalThis.Module ? globalThis.Module.HEAPU8 : null;
    const base = statePointer; // the nint address from C#

    for (let i = 0; i < surgicalMap.length; i++) {
        const entry = surgicalMap[i];
        const ptr = base + entry.offset; // slot offset

        // Read dirty and error bit flags directly from WASM memory
        const isDirty = (h[ptr + entry.bitByte] & entry.bitMask) !== 0;
        const hasError = (h[ptr + 32 + entry.bitByte] & entry.bitMask) !== 0;

        // Toggle UI classes instantly in place
        const el = entry.el;
        if (isDirty) el.classList.add('vavid-dirty'); else el.classList.remove('vavid-dirty');
        if (hasError) el.classList.add('vavid-error'); else el.classList.remove('vavid-error');
    }
}

Enter fullscreen mode Exit fullscreen mode

The Performance Payoff

We ran micro-benchmarks comparing this bypass model to standard Blazor VDOM mutation and F# rules engines:

Method Mean Gen 0 / 1000 Allocated Speedup
VALID Slab direct memory write 6.62 ns - 0 B 26.7x
F# Rule Evaluation 15.80 ns - 0 B 10.4x
Blazor VDOM Mutation (Baseline) 172.78 ns 0.0048 40 B Baseline

At 6.6ns per update with 0 bytes allocated, state tracking is running at register-level CPU efficiency.

By avoiding Blazor's entire component render cycle, UI grids can handle millions of visual updates with zero input latency.

Compile-time Generation with Roslyn

Writing manual pointer offsets and bit masks by hand for every class is exhausting. To keep the developer experience clean, we built a Roslyn Source Generator.

All you do is write a partial class:

[ValidObject]
public partial class Invoice
{
    [Required]
    public string CustomerName { get; set; } = "";

    [Range(0, 10000)]
    public decimal Amount { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

The source generator intercepts this at build time and generates the bitmask backing properties, circular undo/redo history, F# record projections, and auto-generates unit/fuzz tests right into your test projects.

Try it yourself!

V.A.L.I.D. is fully open-source. If you want to check out the WASM memory management, review the source generators, or run the Blazor grid benchmark locally, check out the repository:

V.A.L.I.D. (Vectorized Asynchronous Logic & Intelligent Diagnostics)

Framework Status Performance Diagnostics

V.A.L.I.D. is a high-performance, low-latency business logic framework for .NET. It replaces standard reflection-based change tracking with a compile-time, bitmask-driven, and compiler-integrated architecture.

Built specifically for complex enterprise data management, V.A.L.I.D. ensures surgical precision in synchronization and real-time visibility in the browser.


⚡ Performance Benchmarks

V.A.L.I.D. is engineered for absolute zero-allocation on core operations. Below are the official BenchmarkDotNet results comparing V.A.L.I.D.'s direct memory write speed against other state-management components:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.29591.1000)
.NET SDK 10.0.200-preview.0.26103.119
  [Host]     : .NET 8.0.24 (8.0.2426.7010), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.24 (8.0.2426.7010), X64 RyuJIT AVX2
Method Mean Error StdDev Gen0 Allocated
VALID Slab direct memory write 6.619 ns 0.1573 ns 0.2305 ns - - (0 B)
F# Rule Evaluation 15.800 ns 0.3659 ns 0.7308 ns - - (0 B)
F# CRDT Convergence 86.478 ns 1.7367 ns 3.7384 ns 0.0391 328 B
Blazor VDOM Mutation

I’d love to hear your thoughts on this. Is sharing raw pointers with JS in WebAssembly too unsafe for typical business applications, or is the performance payoff worth the trade-off?