DEV Community

Cover image for WebForms Core 2.2 Released: Render, a New Approach to UI Rendering
Elanat Framework
Elanat Framework

Posted on

WebForms Core 2.2 Released: Render, a New Approach to UI Rendering

WebForms Core is a modern, server-orchestrated technology for building interactive HTML user interfaces, introduced by Elanat in 2024. It enables direct and deterministic DOM management from the server, eliminating the need for separate client-side frameworks such as React, Vue, or Angular.

WebForms Core 2.2 has been released.

This release introduces several new capabilities, but one feature deserves special attention: Render.

Render is a high-level UI rendering primitive designed to make repeated server-side rendering of an existing HTML element predictable and manageable. It brings together several mechanisms that are all new in this version—Snapshot, Rollback, and Transient DOM—into a single high-level primitive.

The result is a simpler way to replace, rebuild, and restore parts of an HTML document while keeping the operation under server-side control.

Render

In this new version, Snapshot, Rollback, and Render are all new additions. Render is a high-level method that uses Snapshot and Rollback internally.

In WebForms Core 2.2, Render brings these operations together.

A simple example is:

form.Render(f =>
{
    f.Replace(InputPlace.Root, "{{name}}", name);
    f.Replace("-", "{{email}}", email);
    f.Replace("-", "{{phone}}", phone);
    f.Replace("-", "{{age}}", age);
    f.Replace("-", "{{city}}", city);
    f.Replace("-", "{{country}}", country);
}, "user");
Enter fullscreen mode Exit fullscreen mode

The rendering process can also be used directly with a template:

var data = new Dictionary<string, string>
{
    { "name", name},
    { "email", email},
    { "phone", phone},
    { "age", age},
    { "city", city},
    { "country", country}
};
string json = JsonSerializer.Serialize(data);

form.Render(f =>
{
    f.BindJSONToTemplate(InputPlace.Root, json, "", "{{value}}");
}, "user");
Enter fullscreen mode Exit fullscreen mode

The important part is not simply the number of lines saved by Render. The important part is that the lifecycle of the rendered element is handled as one operation.

Internally, Render checks whether a previous version of the selected element already exists. If it does, that version is restored. Otherwise, a Snapshot is created.

The rendering process then enters the Transient DOM, appends the generated WebForms commands, exits the Transient DOM, and appends the new form.

In simplified form:

Existing State
      ↓
Rollback or Snapshot
      ↓
Start Transient DOM
      ↓
Apply New Rendering
      ↓
End Transient DOM
      ↓
New UI State
Enter fullscreen mode Exit fullscreen mode

This is particularly useful when the same element has to be rendered repeatedly with different data or templates.

Render and Transient DOM

Render uses the Transient DOM internally.

This is important when selecting InputPlace values inside the Render operation. The root of the selected InputPlace must be considered relative to the Transient DOM being processed.

Render is therefore not simply a shortcut for a sequence of unrelated commands. It creates a controlled rendering process in which the existing state and the new state can be handled together.

The implementation is:

// The Render Method is Sensitive to DOM Changes; It is Recommended to Assign a Stable ID to the Selected Element.
// This Method Utilizes the Transient DOM; Therefore, When Selecting InputPlaces, You Must Consider the Root of the Selected InputPlace Within the Method.
public WebForms Render(WebForms newForm, string InputPlace, string Key = "", bool Permanent = false)
{
    if (newForm == null)
        return this;

    string bodyData = newForm.GetWebFormsData();

    if (string.IsNullOrEmpty(bodyData))
        return this;

    if (string.IsNullOrEmpty(Key))
        Key = InputPlace;

    WebForms form = new WebForms();

    form.Exist(Permanent ? Fetch.Cache(Key) : Fetch.Save(Key));
        form.Rollback(InputPlace, Key, Permanent);
    form.Else();
        form.Snapshot(InputPlace, Key, Permanent);

    form.StartTransientDOM(InputPlace);

    AppendForm(form);
    newForm.EndTransientDOM();
    AppendForm(newForm);

    return this;
}
Enter fullscreen mode Exit fullscreen mode

There is also a configuration-based overload:

public WebForms Render(
    System.Action<WebForms> configure,
    string InputPlace,
    string Key = "",
    bool Permanent = false)
{
    var newForm = new WebForms();
    configure(newForm);

    return Render(newForm, InputPlace, Key, Permanent);
}
Enter fullscreen mode Exit fullscreen mode

This makes Render suitable for both manually constructed WebForms and dynamically configured rendering operations.

Snapshot and Rollback

Render is built on top of the Snapshot and Rollback mechanisms introduced in WebForms Core 2.2.

A Snapshot stores the state of a selected tag so that it can later be restored.

Consider a simple multilingual user template.

The page contains three buttons:

<input id="English" type="button" value="en-user">
<input id="Spanish" type="button" value="es-user">
<input id="Persian" type="button" value="fa-user">

<div id="user">
</div>
Enter fullscreen mode Exit fullscreen mode

The templates are defined using HTML <template> elements:

<template id="en-user">
    <div id="user">
        <field>Name: {{name}}</field>
        <a href="mailto:{{email}}">Email: {{email}}</a>
        <field>Phone: {{phone}}</field>
        <field>Age: {{age}}</field>
        <field>City: {{city}}</field>
        <field>Country: {{country}}</field>
    </div>
</template>

<template id="es-user">
    <div id="user">
        <field>Nombre: {{name}}</field>
        <a href="mailto:{{email}}">Correo electrónico: {{email}}</a>
        <field>Teléfono: {{phone}}</field>
        <field>Edad: {{age}}</field>
        <field>Ciudad: {{city}}</field>
        <field>País: {{country}}</field>
    </div>
</template>

<template id="fa-user">
    <div id="user">
        <field>نام: {{name}}</field>
        <a href="mailto:{{email}}">ایمیل: {{email}}</a>
        <field>تلفن: {{phone}}</field>
        <field>سن: {{age}}</field>
        <field>شهر: {{city}}</field>
        <field>کشور: {{country}}</field>
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

The WebForms code creates a Snapshot for each template:

using CodeBehind;

public partial class SnapshotAndRollbackController : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {
        WebForms form = new WebForms();

        form.Snapshot("en-user");
        form.Snapshot("es-user");
        form.Snapshot("fa-user");

        form.SetCommentEvent(
            InputPlace.AllAttributes("type", "button"),
            HtmlEvent.OnClick,
            "set-state");

        form.StartIndex("set-state");

        form.Rollback("user", Fetch.GetValue("$"));

        form.Replace("user", "{{name}}", Fetch.GetValue("name"));
        form.Replace("user", "{{email}}", Fetch.GetValue("email"));
        form.Replace("user", "{{phone}}", Fetch.GetValue("phone"));
        form.Replace("user", "{{age}}", Fetch.GetValue("age"));
        form.Replace("user", "{{city}}", Fetch.GetValue("city"));
        form.Replace("user", "{{country}}", Fetch.GetValue("country"));

        Write(form.ExportToHtmlComment());
    }
}
Enter fullscreen mode Exit fullscreen mode

When one of the buttons is clicked, the corresponding Snapshot is restored and the user data is inserted into the selected template.

The API itself is intentionally small:

public void Snapshot(
    string InputPlace,
    string Key = "",
    bool Permanent = false)

public void Rollback(
    string InputPlace,
    string Key = "",
    bool Permanent = false)
Enter fullscreen mode Exit fullscreen mode

The Snapshot and Rollback mechanism is sensitive to DOM changes, so assigning a stable ID to the selected element is recommended.

Render uses these mechanisms automatically, so developers do not need to manually coordinate Snapshot and Rollback when using the high-level rendering API.

GetTagHash

WebForms Core 2.2 also introduces Fetch.GetTagHash.

It calculates a hash from the outerHTML of a tag.

form.SetCommentEvent(
    "GetHash",
    HtmlEvent.OnClick,
    "get-hash");

form.StartIndex("get-hash");
form.AddText("Text", "-a");
form.Message(Fetch.GetTagHash("Box"));
Enter fullscreen mode Exit fullscreen mode

The hash is based only on the tag's outerHTML.

This makes it useful for detecting whether the DOM representation of a tag has changed.

Formatting Values with Regex

WebForms Core 2.2 adds:

  • SetFormatCacheValue
  • SetFormatSaveValue

These methods apply Regex-based formatting to values stored in Cache or Save.

For example, the following page contains a money value and a time value:

<button id="SetFormat">Click to Set Format</button>

<input type="text" id="Money" value="12345678">
<input type="text" id="Clock" value="15:1:3">
Enter fullscreen mode Exit fullscreen mode

The WebForms code stores each value and applies a regular-expression replacement:

using CodeBehind;

public partial class SetFormatController : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {
        WebForms form = new WebForms();

        form.SetCommentEvent(
            "SetFormat",
            HtmlEvent.OnClick,
            "set-format");

        form.StartIndex("set-format");

        form.SaveValue("Money", "money");
        form.SetFormatSaveValue(
            "money",
            "(\\d)(?=(\\d{3})+$)",
            "$$1,");

        form.SetValue("Money", Fetch.Save("money"));

        form.CacheValue("Clock", "clock");
        form.SetFormatCacheValue(
            "clock",
            "(^|:)(\\d)(?=:|$)",
            "$$10$2"); // Two $$ for escape JavaScript interpretation

        form.SetValue("Clock", Fetch.Cache("clock"));

        Write(form.ExportToHtmlComment());
    }
}
Enter fullscreen mode Exit fullscreen mode

The first operation formats:

12345678
Enter fullscreen mode Exit fullscreen mode

as:

12,345,678
Enter fullscreen mode Exit fullscreen mode

The second transforms:

15:1:3
Enter fullscreen mode Exit fullscreen mode

into:

15:01:03
Enter fullscreen mode Exit fullscreen mode

The $$ in the replacement string is important because it escapes the $ for JavaScript interpretation.

This is also useful when formatting must happen as part of a larger WebForms operation, where the formatted value can then be applied back to the corresponding HTML element.

Arithmetic Operations

WebForms Core 2.2 adds arithmetic operations for values stored in Cache and Save.

The supported operations are:

+
-
*
/
%
//
**
Enter fullscreen mode Exit fullscreen mode

The following example provides a button for each operation:

<button id="Addition">+</button>
<button id="Subtraction">-</button>
<button id="Multiplication">*</button>
<button id="Division">/</button>
<button id="Remainder">%</button>
<button id="FloorDivision">//</button>
<button id="Power">**</button>

<input type="text" id="Value1" value="10"> |
<input type="text" id="Value2" value="7">
=
<b id="Result"></b>
Enter fullscreen mode Exit fullscreen mode

The WebForms code is:

using CodeBehind;

public partial class ArithmeticController : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {
        WebForms form = new WebForms();

        form.SetCommentEvent(
            "<button>*",
            HtmlEvent.OnClick,
            "result");

        form.StartIndex("result");

        form.SaveValue("Value1", "result");

        form.SetArithmeticSaveValue(
            "result",
            Fetch.GetText("$"),
            Fetch.GetValue("Value2"));

        form.SetText(
            "Result",
            Fetch.Save("result"));

        Write(form.ExportToHtmlComment());
    }
}
Enter fullscreen mode Exit fullscreen mode

The // operation is implemented in WebFormsJS as:

case "//":
    return Math.floor(a / b);
Enter fullscreen mode Exit fullscreen mode

For example, with 10 and 7, / produces the normal division result, while // produces the floored result.

The operations allow common numerical transformations to be performed directly within the WebForms command flow.

Text Operations

WebForms Core 2.2 currently provides six text operations:

textafter
textafterlast
textbefore
textbeforelast
substring
remove
Enter fullscreen mode Exit fullscreen mode

The example below provides a button for each operation:

<button>textafter</button>
<button>textafterlast</button>
<button>textbefore</button>
<button>textbeforelast</button>
<button>substring</button>
<button>remove</button>

<input type="text" id="Value1" value="This is a Hello World!"> |
<input type="text" id="Value2" value=""> |
<input type="text" id="Value3" value="">
=
<b id="Result"></b>
Enter fullscreen mode Exit fullscreen mode

The WebForms implementation is:

using CodeBehind;

public partial class TextOperationController : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {
        WebForms form = new WebForms();

        form.SetCommentEvent(
            "<button>*",
            HtmlEvent.OnClick,
            "result");

        form.StartIndex("result");

        form.SaveValue("Value1", "result");

        form.SetTextOperationSaveValue(
            "result",
            Fetch.GetText("$"),
            Fetch.GetValue("Value2"),
            Fetch.GetValue("Value3"));

        form.SetText(
            "Result",
            Fetch.Save("result"));

        Write(form.ExportToHtmlComment());
    }
}
Enter fullscreen mode Exit fullscreen mode

The current set is intentionally compact. More text operations can be added in future versions based on developer feedback and practical use.

Regex-Based Element Selection

The Criteria system can use regular expressions to select elements.

Consider the following HTML:

<div id="Regex">
    <div>2026-10-01</div>
    <div>Adrian</div>
    <div>43</div>
    <div>00125478744</div>
    <div>Brazil</div>
</div>
Enter fullscreen mode Exit fullscreen mode

The following commands select elements based on their text:

form.SetBackgroundColor(
    "Regex|*?t#\\d{4}-\\d{2}-\\d{2}$",
    "pink");

form.SetBackgroundColor(
    "Regex|*?t#^[0-9]+$",
    "brown");
Enter fullscreen mode Exit fullscreen mode

The t criterion works with the text of the element, while # specifies a regular-expression comparison.

The first expression matches a date such as:

2026-10-01
Enter fullscreen mode Exit fullscreen mode

The second matches text consisting entirely of digits, such as:

43
00125478744
Enter fullscreen mode Exit fullscreen mode

The underlying WebFormsJS implementation uses JavaScript's RegExp:

case '#':
{
    let pattern = tmpValue;
    let flags = "";

    const m = pattern.match(/^\/([\s\S]+)\/([a-z]*)$/);

    if (m)
    {
        pattern = m[1];
        flags = m[2];
    }

    try
    {
        result = new RegExp(pattern, flags).test(text);
    }
    catch
    {
        result = false;
    }

    break;
}
Enter fullscreen mode Exit fullscreen mode

This allows the Criteria system to perform more precise element selection without requiring a separate selector language.

JavaScript Objects from WebForms

A value beginning with $ in a WebForms class method can be interpreted by WebFormsJS as a JavaScript object reference.

This provides a direct way for server-generated commands to refer to objects that exist in the browser.

The distinction is important: the WebForms class generates the command, while WebFormsJS performs the corresponding client-side execution.

Try and Catch

WebForms Core 2.2 adds Try and Catch for explicit error handling.

For example:

form.Try();

form.AddTag("<h5>", "div");
form.SetText("<h5>|<>-1", "New tag!");
form.Message("New Tag Was Added!");

form.Catch();

form.Message("h5 Tag Not Exist 1!");
form.Message("h5 Tag Not Exist 2!");
Enter fullscreen mode Exit fullscreen mode

If an Action Control inside Try encounters an error, execution stops and continues at Catch.

Without Try and Catch, there is no automatic recovery mechanism. The developer decides which operations may cause problems and places those operations inside an appropriate error-handling structure.

LockQueue

The queue behavior was changed in WebForms Core 2.2.

In the previous version, the queue was automatically locked. In 2.2, the queue is no longer automatically locked in that way.

The default duration is controlled by:

WebFormsOptions.QueueLockDuration = 100;
Enter fullscreen mode Exit fullscreen mode

When a process needs additional protection, the developer can explicitly call:

form.LockQueue(10000);
Enter fullscreen mode Exit fullscreen mode

LockQueue dynamically increases the lock duration for the current process and then restores the previous configured value automatically.

This is useful when a process must be allowed to complete before another queued process starts doing something else.

Native HTML Validation from the Server

WebForms Core 2.2 adds SetCustomValidity.

WebForms Core does not introduce a new validation framework.

Instead, it brings the browser's existing validation capability into the server-generated command model.

For example:

form.SetCommentEvent(
    "(name)",
    HtmlEvent.OnInput,
    "custom-validity");

form.StartIndex("custom-validity");

form.IsEqualTo("admin", Fetch.GetValue("$"));

form.SetCustomValidity(
    "$",
    "The username cannot be \"admin\".");

form.Else();

form.SetCustomValidity("$", "");
Enter fullscreen mode Exit fullscreen mode

The browser's native Constraint Validation API remains responsible for the validation behavior.

The server determines the condition and sends the appropriate command to the HTML element.

Stable WebAssembly Support

WebForms Core 2.2 brings stable WebAssembly support across seven languages and environments:

  • AssemblyScript
  • C
  • C++
  • C#
  • Go
  • Java
  • Rust

Go and Rust provide WebAssembly targets directly, while AssemblyScript is designed specifically for WebAssembly.

For example, Go can be built with its native WebAssembly target:

@echo off

set GOOS=js
set GOARCH=wasm

go build -o webforms-go.wasm

for /f "delims=" %%i in ('go env GOROOT') do set GOROOT=%%i

copy "%GOROOT%\lib\wasm\wasm_exec.js" wasm_exec.js

pause
Enter fullscreen mode Exit fullscreen mode

Rust can be built with the WebAssembly target and processed with wasm-bindgen:

cargo +stable-x86_64-pc-windows-gnu build --release --target wasm32-unknown-unknown

wasm-bindgen target/wasm32-unknown-unknown/release/webformscore_wasm_test.wasm --target web --out-dir pkg
Enter fullscreen mode Exit fullscreen mode

The current implementations are:

  • AssemblyScript: WebAssembly
  • C: Emscripten
  • C++: Emscripten
  • C#: AOT and Runtime
  • Go: native WebAssembly support
  • Java: TeaVM
  • Rust: native WebAssembly support and wasm-bindgen

WebAssembly does not change the WebForms Core command model. It provides another execution environment while WebForms Core continues to generate WebForms responses and commands.

WebForms Classes Across 18 Programming Languages

WebForms Classes are now directly available for 18 programming languages, making WebForms Core accessible across a broad range of programming environments.

The collection includes AssemblyScript, C, C++, C#, Dart, Elixir, Go, Java, JavaScript, Julia, Perl, PHP, Python, R, Ruby, Rust, Swift, and TypeScript.

WebForms Core 2.2 is currently available for .NET and Java. The remaining language implementations will be updated to version 2.2 over the next 10 days.

This means the WebForms Classes ecosystem is not limited to a single backend language. Developers can use the same WebForms Core architecture and programming model across a wide range of languages and environments, while each implementation remains native to its own ecosystem.

Why WebForms Core Did Not Add Middleware

One of the hardest architectural decisions during the development of 2.2 was whether to introduce a generic Middleware structure into WebForms Core.

Technically, it was possible to build a very general Function-level Middleware system.

For example:

function A(x, y) {
    const result = Middleware.call(this, A, B, arguments);

    if (result !== undefined)
        return result;

    return x * y;
}

function B(x, y) {
    return [2, 20];
}

function Middleware(method, middleware, args) {
    if (!(this.__middleware ??= []).includes(method.name)) {
        this.__middleware.push(method.name);

        const newArgs = middleware.apply(this, args);
        const result = method.apply(this, newArgs);

        this.__middleware.splice(
            this.__middleware.indexOf(method.name),
            1);

        return result;
    }
}
Enter fullscreen mode Exit fullscreen mode

Such a system could do much more than a conventional Middleware implementation. It could change arguments, stop execution, and execute the original function again with new arguments.

But there was a problem.

Adding this abstraction to WebFormsJS would make the core considerably more complicated. Lifecycle management, recursion, execution order, argument changes, and the different paths through which Functions are executed would all have to be managed.

In the end, we decided not to add it.

Instead, when a requirement is genuinely specific to client-side processing, a JavaScript Module is a better place for that logic.

For example, if form data needs to be reduced or modified before submission, a JavaScript Module can perform that operation directly without adding a generic Middleware layer to WebFormsJS.

This decision is not a statement that Middleware is impossible in WebForms Core.

It is an architectural decision to keep the core smaller and move client-specific behavior into JavaScript Modules.

Other Changes

WebForms Core 2.2 also includes several smaller improvements and fixes.

Reflection renamed to Reflect

The Reflection method has been renamed to:

Reflect
Enter fullscreen mode Exit fullscreen mode

Debugger numbering

Debugger logs now include numbering, making it easier to follow the execution sequence of generated Action Controls.

JavaScript Object escaping

An escaping issue involving JavaScript Objects containing two consecutive $ characters was fixed.

WebForms Core 2.1.1 and 2.1.2

The 2.1.x maintenance releases also included several changes that lead into the 2.2 release:

  • Fixed the hash-link behavior so hash links no longer create an unnecessary server request.
  • Added the Modulo Filter to Criteria.
  • Fixed Replace behavior for values stored in Save and Cache.
  • Added Fetch support in InputPlace.
  • Removed CSharpMediator.
  • CSharpWASM can infer the JavaScript file path from the WASM file path.
  • CSharpWASM supports both CLI and AOT.
  • Rust WASM can be built through wasm-bindgen or directly as WebAssembly.

Final Thoughts

WebForms Core 2.2 is not simply a collection of additional methods.

The most important change is the introduction of higher-level operations that allow developers to describe what should happen to a UI without manually coordinating every underlying step.

Render is the clearest example.

Snapshot, Rollback, and Transient DOM already provided the mechanisms needed to manage complex DOM transformations. Render combines those mechanisms into a single operation that can manage the transition from an existing UI state to a new one.

At the same time, 2.2 keeps the underlying system explicit.

WebForms Core (WFC) 2.2

Native browser validation remains native browser validation. Client-specific processing can remain in JavaScript Modules. WebAssembly provides additional execution environments without changing the WebForms Core command model. Queue locking is explicit when a process actually needs it.

The result is a more capable WebForms Core without turning the core itself into a larger collection of abstractions.

Related links

On Elanat:

On GitHub:

Top comments (0)