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();
Use this.
builder.Services.AddCodeBehind();
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();
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)
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>
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);
}
}
Explanation:
-
SetSSEEventbinds 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
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());
}
});
}
}
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
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());
}
});
}
}
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();
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);
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>
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);
}
}
Gzip file decompression middleware
var allowedFiles = new[] { ".jpg", ".png", ".pdf", ".txt" };
app.UseGzipFileDecompression(allowedFiles, 5 * 1024 * 1024);
- 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>
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);
}
}
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();
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
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.
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
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-->
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)