DEV Community

Cover image for CodeBehind 4.4 Released
Elanat Framework
Elanat Framework

Posted on

CodeBehind 4.4 Released

After about 3 months of work around the clock, we added new features to the CodeBehind framework and released version 4.4 in .NET 7. This version has become a full-stack system with the update to WebForms Core version 2 technology, which will amaze you.

Features of version 4.4

WebForms Core technology update to version 2

WebForms Core technology version 2 is one of the biggest updates in the history of software development. Much bigger than the iOS 7 update and Android 5.0 (Lollipop) update, and even the biggest Windows update of any version! This version is not about “adding features”, but about changing the abstraction level of the framework. This big update is about transforming the technology from a framework to something like a Web OS.

Note: We will soon provide a comprehensive article about WebForms Core technology version 2.

Section renamed to Segment

To make the CodeBehind framework more compliant with web standards, we renamed the Section feature to Segment. All methods and names have also been changed from Section to Segment. "Segment" is a term used in web standards.

Added AddCodeBehind service

The AddCodeBehind service has been added to better align with the configuration structure in .NET.

From now on, instead of:

SetCodeBehind.CodeBehindCompiler.Initialization();
Enter fullscreen mode Exit fullscreen mode

Use this.

builder.Services.AddCodeBehind();
Enter fullscreen mode Exit fullscreen mode

This service must be added before calling var app = builder.Build();

Configuring CodeBehind in ASP.NET Core is completely done (with the minimum service)

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCodeBehind();

var app = builder.Build();

app.UseCodeBehind();

app.Run();
Enter fullscreen mode Exit fullscreen mode

Ignore Layout for WebForms Core secondary requests from the server

A new setting has been added to ignore Layout for secondary WebForms Core requests from the server.

ignore_layout_for_post_back=false

This setting is disabled by default and must be enabled in the options.ini file to use it.

Support for SSE and Advanced Broadcast Methods

SSE support in the CodeBehind framework is very similar to WebSocket support. However, the two have different structures. SSE is a one-way connection where only the server can send data to the client, while WebSocket is a two-way connection.

Two new settings have also been added.

sse_interval=1000

Recurring time interval for running broadcasts

max_sse_connections_per_client=3

Limits the maximum number of concurrent SSE connections allowed per client to prevent resource exhaustion.

To send SSE responses you must use the BroadcastSSE methods. BroadcastSSE methods are available in all 3 parts: Controller, Model, and View.

BroadcastSSE(context, Message, IgnoreThis = false)
BroadcastSSE(context, Message, RoleName, Id, ClientId, IgnoreThis = false)
BroadcastSSEForRole(context, Message, RoleName, IgnoreThis = false)
BroadcastSSEForSSEId(context, Message, Id, IgnoreThis = false)
BroadcastSSEForClientId(context, Message, ClientId, IgnoreThis = false)
Enter fullscreen mode Exit fullscreen mode

These methods allow SSE messages to be broadcast:

  • to all connected clients
  • to specific roles
  • to specific SSE sessions
  • or to individual clients

This provides fine-grained control over real-time message delivery without relying on client-side polling or JavaScript-based event routing.

Example: SSE Main Page

The main page defines UI elements that trigger SSE-enabled server endpoints.

View

@page
@controller SSEController
@layout "/layout.aspx"
@{
  ViewData.Add("title","SSE");
}
    <button id="Button1">SSE Event 1</button>
    <b>SSE Background Random Result</b>
    <button id="TimeButton">Get Live Server Time</button>
    <b>Server Time: </b><span id="TimeResult"></span>
Enter fullscreen mode Exit fullscreen mode

Controller

using CodeBehind;

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

        form.SetSSEEvent("Button1", HtmlEvent.OnClick, "/sse/event1");
        form.SetSSEEvent("TimeButton", HtmlEvent.OnClick, "/sse/gettime");

        Control(form);
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • SetSSEEvent binds a client-side UI event to an SSE-enabled server endpoint.
  • No JavaScript event handlers are required.
  • When the user clicks a button, an SSE connection is established automatically.

SSE Event 1: Background Color Updates

View "/sse/event1"

@page
@controller SSEEvent1Controller
Enter fullscreen mode Exit fullscreen mode

Controller

using CodeBehind;

public partial class SSEEvent1Controller : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {       
        EnableSSE(); // Activate SSE for this endpoint

        Task.Run(async () =>
        {
            while (true)
            {
                await Task.Delay(5000);

                var form = new WebForms();

                int number = new Random().Next(0, 0xFFFFFF + 1);

                string hex = "#" + number.ToString("X6");

                form.SetBackgroundColor("<b>", hex);

                BroadcastSSE(context, form.ExportToLineBreak());
            }
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • EnableSSE() activates SSE streaming for this endpoint.
  • A background task runs continuously on the server.
  • Every 5 seconds:

    • A random color is generated.
    • A DOM update command is created using WebForms.
    • The update is broadcast to all connected clients using SSE.

No DOM diffing, virtual DOM, or client-side rendering is involved.

SSE Get Time Example

View "/sse/gettime"

@page
@controller GetTimeSSEController
Enter fullscreen mode Exit fullscreen mode

Controller

using CodeBehind;

public partial class GetTimeSSEController : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {       
        EnableSSE(); // Activate SSE for this endpoint

        Task.Run(async () =>
        {
            while (true)
            {
                await Task.Delay(5000);

                var form = new WebForms();
                string serverTime = DateTime.Now.ToString("HH:mm:ss");
                form.SetText("TimeResult", $"Current Server Time: {serverTime}");

                BroadcastSSE(context, form.ExportToLineBreak());
            }
        });

    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The server continuously pushes the current time to the client.
  • The <span id="TimeResult"> element is updated in real time.
  • The client remains passive; it only receives and applies commands.

Why This Approach Matters

This SSE implementation is command-driven, not data-driven:

  • No HTML re-rendering
  • No JSON diffing
  • No virtual DOM
  • No client-side state reconciliation

Instead, the server sends explicit DOM manipulation commands, which are applied directly on the client.

This results in:

  • Lower server CPU usage
  • Predictable performance
  • High scalability
  • Minimal client-side complexity

Ability to specify segments in UseCodeBehindRoute

In the controller configuration in route, we could previously only use the first segment of the URL for the controller name; but in this version we can use any segment we want.

Example:

The following code shows the routing configuration for the controller:

app.UseCodeBehindRoute();
Enter fullscreen mode Exit fullscreen mode

In the above configuration, the first segment specifies the controller name. For example, the address example.com/main/page/1 executes the controller named main.

However, we have made it possible to target subsequent segments in some cases. Please pay attention to the following call method.
The following code shows the controller configuration in the route targeting the third segment.

app.UseCodeBehindRoute(2);
Enter fullscreen mode Exit fullscreen mode

In the above configuration, the third segment specifies the controller name (index starts from 0). For example, the address example.com/page/1/main executes the controller named main.

Note: It is necessary to clarify that we at Elanat recommend using the dedicated app.UseCodeBehind() configuration.

Gzip middleware

WebForms Core version 2 introduced client-side compression using Gzip. This feature reduces the size of data sent from the browser to the server. This version has stream and file compression features.
Obviously, to use this feature, the server must also be able to receive and decompress compressed data. Therefore, we at Elanat have created two new middlewares to support these new features so that the CodeBehind framework can use the compression capabilities of WebForms Core technology on the server side. These middlewares are fully compatible with the compression capabilities of WebForms Core technology.

File Upload Compression

Files uploaded from the browser can be compressed before transmission.
On the server, the Gzip File Decompression Middleware transparently decompresses supported files before they reach the controller.

This view defines a simple multipart file upload form:

View

@page
@controller GzipFileController
@layout "/layout.aspx"
@{
  ViewData.Add("title","Gzip file");
}
<form method="post" action="/gzip" enctype="multipart/form-data">
    <label for="File">File Upload</label>
    <input name="File" id="File" type="file" multiple="true" />
    <br />
    <input name="Button" type="submit" value="Click to uploads files" />
</form>
Enter fullscreen mode Exit fullscreen mode

All uploaded files are accessed from the request form.
At this point, the files have been automatically decompressed by the middleware before reaching the controller.

Controller

using CodeBehind;

public partial class GzipFileController : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {
        if (context.Request.Form["Button"].Has())
            Button_Click(context);
    }

    private void Button_Click(HttpContext context)
    {
        var files = context.Request.Form.Files;

        WebForms form = new WebForms();

        if (files.Count > 0)
        {
            var ValidExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif" , ".gz" };

            foreach (var file in files)
            {
                if (file.Length > 0)
                {
                    form.AddTag("<form>", "h3");

                    var FileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();

                    if (!ValidExtensions.Contains(FileExtension))
                    {
                        form.SetBackgroundColor("<h3>-1", "yellow");
                        form.AddText("<h3>-1", $"Invalid file type: {file.FileName}");
                        form.AddTag("<form>", "br");
                        continue;
                    }

                    string FilePath = "/image/" + file.FileName;

                    using (Stream stream = new FileStream("wwwroot" + FilePath, FileMode.Create, FileAccess.ReadWrite))
                    {
                        file.CopyTo(stream);
                    }

                    form.AddText("<h3>-1", $"File {file.FileName} was uploaded.");
                    form.AddTag("<form>", "img");
                    form.SetAttribute("<form>|<img>-1", "src", FilePath);
                    form.SetWidth("<form>|<img>-1", 400);
                    form.AddTag("<form>", "br");
                }
            }
        }
        else
        {
            form.SetBackgroundColor("<h3>-1", "red");
            form.AddText("<h3>-1", "Please select at least one file.");
            form.Delete("<h3>-1");
            form.AssignDelay(3);
        }

        Control(form, true);
    }
}
Enter fullscreen mode Exit fullscreen mode

Gzip file decompression middleware

var allowedFiles = new[] { ".jpg", ".png", ".pdf", ".txt" };
app.UseGzipFileDecompression(allowedFiles, 5 * 1024 * 1024);
Enter fullscreen mode Exit fullscreen mode
  • Supports extension filtering
  • Enforces maximum file size
  • Requires no changes in controller logic

Gzip Data Example

This example demonstrates how gzip-compressed form data can be sent from the client and automatically decompressed on the server before being processed by the controller.

View

@page
@controller GzipDataController
@layout "/layout.aspx"
@{
  ViewData.Add("title","Gzip data");
}

<form method="put" action="/gzip">
    <label for="Text1">Text1</label>
    <input name="Text1" id="Text1" type="text"/><br/>
    <label for="Text2">Text2</label>
    <input name="Text2" id="Text2" type="text"/><br/>
    <label for="Text3">Text3</label>
    <input name="Text3" id="Text3" type="text"/><br/>
    <label for="Textarea1">Textarea1</label>
    <textarea name="Textarea1" id="Textarea1"></textarea>
    <br/>
    <input name="Button" type="submit" value="Click to send data" />
</form>
<br/>
Add Data From Server
<br/>
<textarea id="Textarea2"></textarea>
Enter fullscreen mode Exit fullscreen mode

The view defines a standard HTML form that sends textual data to the server:

  • The form uses method="put" to send data in the request body.
  • Multiple text inputs and a textarea collect user input.
  • A second textarea (Textarea2) is reserved for displaying data returned from the server.

Controller

using CodeBehind;

public partial class GzipDataController : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {
        if (context.Request.Form["Button"].Has())
            Button_Click(context);
    }

    private void Button_Click(HttpContext context)
    {
        WebForms form = new WebForms();

        form.Message(context.Request.Form["Text1"].ToString());
        form.Message(context.Request.Form["Text2"]);
        form.Message(context.Request.Form["Text3"]);
        form.AddText("Textarea2", context.Request.Form["Textarea1"]);

        Control(form, true);
    }
}
Enter fullscreen mode Exit fullscreen mode

At this point, the request body has already been decompressed by the Gzip middleware, so form values are accessed normally through Request.Form.

Gzip data decompression middleware

// Decompress the entire body if it is gzip.
app.UseGzipDecompression();
Enter fullscreen mode Exit fullscreen mode

This middleware automatically:

  • Detects gzip-compressed request bodies
  • Decompresses the data
  • Makes the content transparently available to the controller

No additional handling is required in application code.

Advanced structure for receiving form data in WebSocket

A small but very important change has been added in version 2 of the WebForms Core technology: a setting to add name and value to understand the submission of forms in the WebSocket data sent. The name-value string is added to the beginning of the data

Example:

form=true&username=johndoe&email=john@example.com&age=29&subscribe=yes
Enter fullscreen mode Exit fullscreen mode

form=true → signals that this WebSocket message is a form submission.

This automatically detects in the WebSocket middleware whether the data is Form or not.

Support for the comment mode structure

Thanks to the new feature of the WebForms Core technology in allowing the output of WebForms responses as comments, in the CodeBehind framework we also used the comment structure for the initial combined responses of the View and the WebForms class to comply with HTML standards.

WebForms Core - comment mode structure

Comment mode is better because HTML comments keep the structure valid and standards‑compliant, avoiding browser objections.
They also embed Action Controls cleanly inside the document, making requests more consistent and maintainable.

New setting

use_comment_mode_for_web_forms_combinate=false

This setting specifies whether secondary responses combined with the View should also include comments or be added based on the Action Control.

Action Controls + View → Add View with SetText

[web-forms]
st<main>=<h3>State Test</h3>$[ln];<button id="Button1">Add State 1</button>$[ln];<button id="Button2">Add State 2</button>$[ln];<button id="Button3">Delete This State Without Path</button>$[ln];<button id="Button4">Delete This State With Current Path</button>$[ln];<button id="Button5">Delete All State</button>
bc<h3>=green
EgButton1=onclick|?add_state_1
EgButton2=onclick|?add_state_2
EgButton3=onclick|?delete_this_state_without_path
EgButton4=onclick|?delete_this_state_with_path
EgButton5=onclick|?delete_all_state
Enter fullscreen mode Exit fullscreen mode

Action Controls + View → Add Action Controls in comment mode

<h3>State Test</h3>
<button id="Button1">Add State 1</button>
<button id="Button2">Add State 2</button>
<button id="Button3">Delete This State Without Path</button>
<button id="Button4">Delete This State With Current Path</button>
<button id="Button5">Delete All State</button>
<!--[web-forms]
bc<h3>=green
EgButton1=onclick|?add_state_1
EgButton2=onclick|?add_state_2
EgButton3=onclick|?delete_this_state_without_path
EgButton4=onclick|?delete_this_state_with_path
EgButton5=onclick|?delete_all_state-->
Enter fullscreen mode Exit fullscreen mode

Fixed the problem with the Write method in View

There was a small problem when creating the final View class, and that problem was that the values ​​written were not added to the View response. We fixed this issue in version 4.4.

And a few other minor improvements

This release also includes other small improvements that improve system performance.

Next release

Our plan for version 4.5 is to add advanced Async features. CodeBehind 4.5 will be the last release on .NET 7, and then version 4.6 will be fully and quickly ported to .NET 10.

Related links

CodeBehind on GitHub:
https://github.com/elanatframework/Code_behind

Get CodeBehind from NuGet:
https://www.nuget.org/packages/CodeBehind/

CodeBehind page:
https://elanat.net/page_content/code_behind

Top comments (0)