Zero-Framework C#: Building Standalone Web Applications with TcpListener, cshttp, and Pageless Handlers

Oct 14, 2026
Updated Oct 14, 2026
adriancs

No IIS, No Kestrel, No ASP.NET: High-performance, self-contained web applications in pure C# with zero dependencies.

White Paper: Zero-Framework C# Web Architecture

1. Abstract

Modern web development in the .NET ecosystem assumes that an application cannot exist without a web framework. Whether targeting .NET Framework 4.8 on IIS or .NET 8/10 on Kestrel, developers are forced to accept deep abstraction layers: hosting builders, dependency injection containers, middleware pipelines, routing trees, and external NuGet package graphs.

While Pageless ASP.NET Web Forms proved that stripping away .aspx pages, ViewState, control trees, and SessionStateModule inside IIS yields a remarkably fast, maintainable C# web architecture, it still relies on System.Web.dll and IIS to parse HTTP traffic and manage the process lifecycle.

This white paper presents the natural evolution of that philosophy: removing the web framework and application server entirely. By combining a raw TcpListener, the embedded source code of cshttp, and Pageless C# request handlers behind an Nginx reverse proxy, a standard C# Console Application becomes a self-contained, dependency-free web server and application in a single executable.


2. The Architectural Gap in .NET

In systems programming languages, the boundary between the network socket, the HTTP parser, and the application logic is explicit and modular:

  • C has llhttp
  • Rust has httparse
  • Python has httptools

Historically, C# lacked a standalone, specification-compliant HTTP/1.1 wire parser. Developers who wanted to listen on a TCP socket without adopting the entirety of ASP.NET or Kestrel had only two choices: use HttpListener (which binds to the Windows-specific http.sys kernel driver) or write a naive string-splitting parser that silently breaks on chunked transfer encoding, HTTP request smuggling, bare CR injection, or multipart uploads.

cshttp closes this gap. Written in pure C# 7.3 with zero external dependencies, cshttp consists of 19 .cs files that drop directly into a project's source tree. It implements RFC 9112 (HTTP/1.1), RFC 9110, RFC 3986, RFC 6265, RFC 7578, and the WHATWG URL Standard at the byte level—turning raw byte[] arrays from a socket into structured HttpRequestMessage objects, and serializing HttpResponse objects back into wire-ready byte arrays.


3. System Topology: The 4-Pillar Stack

Replacing IIS and Kestrel in production does not mean exposing a raw C# TcpListener directly to the public internet. Instead, responsibilities are cleanly divided across four battle-tested layers:

[ Public Internet / Browser ]
              │
              ▼  HTTPS (TLS 1.3 / HTTP/2 / HTTP/3)
┌──────────────────────────────────────────────────────────┐
│ Pillar 1: Nginx Reverse Proxy                            │
│  • TLS Termination & Certificate Management              │
│  • Direct Static Asset Serving (/css, /js, /images)      │
│  • Slowloris Buffering & DDoS Rate Limiting              │
└──────────────────────────────────────────────────────────┘
              │
              ▼  Clean HTTP/1.1 over Loopback (127.0.0.1)
┌──────────────────────────────────────────────────────────┐
│ Pillar 2: OS Process Supervisor + C# TcpListener         │
│  • Windows Service or Linux systemd (Restart=always)     │
│  • Async Accept-Loop & Graceful Shutdown Drain           │
└──────────────────────────────────────────────────────────┘
              │
              ▼  Raw Socket byte[]
┌──────────────────────────────────────────────────────────┐
│ Pillar 3: Embedded cshttp Engine (19 Source Files)       │
│  • Layer 1: RFC 9112 Envelope Framing & Smuggling Guard  │
│  • Layer 2: Lazy-Parsed Query, Form, Cookies & Multipart │
└──────────────────────────────────────────────────────────┘
              │
              ▼  HttpRequestMessage
┌──────────────────────────────────────────────────────────┐
│ Pillar 4: Pageless C# Architecture                       │
│  • O(1) Switch Router (Page Route + API Route Pair)      │
│  • In-Memory ConcurrentDictionary Session + DB Rehydrate │
│  • Two-Phase Multiline $"" HTML Composition              │
└──────────────────────────────────────────────────────────┘

Pillar 1: Nginx as the Edge Shield and Static File Server

In a traditional IIS or Kestrel setup, the C# runtime often participates in serving static files, negotiating TLS, or managing slow client connections. In this architecture, Nginx handles all edge mechanics:

  1. Zero C# CPU for Static Assets: Nginx serves /css/, /js/, and /images/ directly from disk using kernel sendfile. The C# application is only invoked for dynamic HTML page routes and API endpoints.
  2. Protocol Normalization: Nginx terminates TLS and HTTP/2 or HTTP/3 from the client, buffers incomplete payloads, and forwards clean, well-formed HTTP/1.1 requests over loopback (127.0.0.1) to the C# console application.

Pillar 2: Raw TcpListener & OS Process Supervision

Inside the C# Console Application, a TcpListener bound to IPAddress.Loopback runs an asynchronous accept loop (AcceptTcpClientAsync), dispatching each incoming connection to the .NET ThreadPool.

Because IIS Application Pools are no longer present to monitor process health, process lifecycle management is delegated to the operating system's native supervisor:

  • Linux: A systemd unit configured with Restart=always and RestartSec=2.
  • Windows: A native Windows Service (ServiceBase or NSSM) configured with automatic failure recovery.

Pillar 3: Embedded cshttp Source-Level Integration

Rather than referencing binary DLLs or NuGet packages that suffer from version drift and supply-chain vulnerabilities, the 19 source files of cshttp (HttpParser.cs, HttpMessage.cs, HttpResponse.cs, etc.) are compiled directly alongside the application code.

  • Layer 1 (Envelope Parser): Validates the request-line, headers, and body framing (including Content-Length and chunked transfer encoding) while actively rejecting conflicting Transfer-Encoding/Content-Length smuggling attempts, bare CR injections, and oversized payloads. And because cshttp uses zero regular expressions on untrusted input, it is immune to ReDoS attacks.
  • Layer 2 (Lazy Content Toolkit): Properties such as req.Path, req.QueryString["key"], req.Form["key"], req.Cookies["name"], and req.Files["field"] are evaluated lazily on first access. A handler that only inspects req.Path and req.Method pays zero CPU or memory allocation cost for parsing form bodies or cookies.

4. Porting Pageless Architecture from IIS to cshttp

The Pageless ASP.NET Web Forms specification was deliberately designed with thin HTTP handlers and an HTTP-free core. Because database repositories, domain models (obBook), and HTML template generators contain zero System.Web dependencies, transitioning from IIS (Global.asax.cs) to a standalone cshttp Console Application requires almost no changes to application code.

4.1 Replacing Global.asax with a Pure Switch Dispatcher

In IIS, Pageless Architecture intercepts requests in Application_BeginRequest. In the standalone console application, the socket loop passes the parsed HttpRequestMessage directly to a static Router.Dispatch(req) method:

public static class Router
{
    public static byte[] Dispatch(HttpRequestMessage req)
    {
        try
        {
            // 1. Restore lock-free session from ssid / lsid cookies
            SessionContext ctx = AppSession.Restore(req);

            // 2. Normalize path
            string path = req.Path.ToLowerInvariant().TrimEnd('/');
            if (path.Length == 0) path = "/";

            // 3. Direct O(1) switch routing (Page + API pairs)
            switch (path)
            {
                case "/":
                case "/home":    return RH.HomePage.HandleRequest(req, ctx);
                case "/books":   return RH.BookPage.HandleRequest(req, ctx);
                case "/bookapi": return RH.BookPageApi.HandleRequest(req, ctx);
            }

            // 4. Prefix routes for parameterized URLs
            if (path.StartsWith("/u/"))
                return RH.UserProfilePage.HandleRequest(req, ctx, path.Substring(3));
            if (path.StartsWith("/book/"))
                return RH.BookDetailPage.HandleRequest(req, ctx, path.Substring(6));

            return HttpResponse.Status(404);
        }
        catch (GuardException gx)
        {
            return ApiHelper.WriteError(gx.Message, gx.StatusCode);
        }
        catch (Exception ex)
        {
            Log.Error(ex);
            return ApiHelper.WriteError("An unexpected error occurred", 500);
        }
    }
}

Notice what disappears compared to IIS: there is no web.config, no AcquireRequestState lock to disable, no Response.TrySkipIisCustomErrors, and no CompleteRequest() workaround to avoid ThreadAbortException. Every handler simply returns a byte[] produced by HttpResponse.

4.2 Near-Identical Handler Ergonomics

cshttp intentionally mirrors the classic request indexing ergonomics that make Pageless handlers concise, including the unified req["key"] indexer that searches QueryString → Form → Cookies → Headers:

public static class BookPage
{
    public static byte[] HandleRequest(HttpRequestMessage req, SessionContext session)
    {
        var pt = new PageTemplate
        {
            Title = "Books",
            Description = "Library Catalog",
            BodyClass = "page-books",
            ExtraHeaderText = StaticAsset.Css("/css/books.css") + "\n" + StaticAsset.Script("/js/books.js")
        };

        List<obBook> books = BookRepo.GetAll();
        var sb = new StringBuilder(4096);

        sb.Append(pt.GenerateHtmlHeader());
        sb.Append(@"
<div class='book-list'>");

        foreach (var book in books)
        {
            // Phase 1: Collect and encode values (using BCL WebUtility instead of System.Web)
            string title  = WebUtility.HtmlEncode(book.Title);
            string author = WebUtility.HtmlEncode(book.Author);
            string status = book.Stock == 0 ? "Sold out"
                          : book.Stock < 5  ? "Low stock"
                          : "In stock";

            // Phase 2: Compose multiline HTML fragment with single-quoted attributes
            sb.Append($@"
    <div class='card-book' data-id='{book.Id}'>
        <strong>{title}</strong><br>
        Author: {author}<br>
        Status: {status}<br>
        <button type='button' class='btn-edit'>Edit</button>
    </div>");
        }

        sb.Append(@"
</div>");
        sb.Append(pt.GenerateHtmlFooter());

        return HttpResponse.Html(sb.ToString());
    }
}

4.3 Lock-Free Hybrid Session State (AppSession)

In classic ASP.NET, HttpContext.Session imposes per-session exclusive locks and breaks when sessionState mode="Off" is used. Pageless Architecture replaces it with a two-tier hybrid session model that becomes even cleaner in a standalone Console App:

  1. Fast Path (ssid Cookie → RAM): Normal requests read the ssid cookie via req.Cookies["ssid"] and perform an O(1), lock-free lookup against an in-process ConcurrentDictionary<string, StateObject>.
  2. Durable Rehydration (lsid Cookie → Database): If the console application restarts (via systemd or a Windows Service update) and the RAM dictionary clears, any logged-in user presenting a valid lsid cookie is transparently rehydrated from the login_sessions database table (verified against a SHA-256 token hash) and issued a fresh in-memory ssid via HttpResponse.SetCookie(...).

4.4 Cache-Busting StaticAsset Without HostingEnvironment

In IIS, StaticAsset.Version(path) uses HostingEnvironment.MapPath("~" + path) to inspect the file's last-write timestamp on disk. In the standalone console app, HostingEnvironment.MapPath is replaced with Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", path.TrimStart('/')). As in the IIS specification, the file modification timestamp (File.GetLastWriteTimeUtc) is read directly per request without static memoization, ensuring that editing a .css or .js file on disk immediately updates the ?v=yyyyMMddHHmmss query string on the very next page refresh without restarting the server process.


5. Production Reliability: Supervision & Graceful Shutdown

A common misconception is that IIS or Kestrel is required for production uptime. In reality, process resilience in a standalone C# console server comes down to two lightweight mechanisms:

5.1 Graceful Connection Draining

When deploying a new binary or restarting the service, the OS sends a termination signal (SIGTERM on Linux, OnStop in a Windows Service, or Console.CancelKeyPress interactively). The server coordinates a zero-downtime drain using a CancellationTokenSource and an active request counter:

  1. Stop Listening: Cancel the CancellationToken passed to AcceptTcpClientAsync() and call listener.Stop(). The OS immediately stops routing new TCP handshakes to the process.
  2. Drain In-Flight Requests: Increment an Interlocked counter (_activeRequests) when a socket enters a handler, and decrement it once stream.WriteAsync(responseBytes) completes.
  3. Bounded Wait: On shutdown, poll _activeRequests in a short loop (e.g., up to 10 seconds) until all in-flight Pageless handlers finish writing their responses and close their sockets before exiting Main().

5.2 Dual-Mode Executable (Interactive Console + Background Service)

Because the entire server is a standard .exe (targeting .NET Framework 4.8 or .NET 8/10), developers can inspect Environment.UserInteractive in Program.Main():

  • When true (Developer Workstation): Runs in a standard console window, printing live request logs, cshttp parse warnings, and SQL timings directly to Console.Out.
  • When false (Production Server): Starts via ServiceBase.Run() on Windows or runs directly under systemd on Linux.

6. Architectural Comparison

Architectural DimensionClassic ASP.NET + IISASP.NET Core + KestrelStandalone Console + cshttp + Pageless
External DependenciesSystem.Web, IIS, Windows OS

| Microsoft.AspNetCore.*, DI, Hosting

| Zero (19 embedded .cs files + BCL)

| | Request Pipeline | 20+ HttpApplication stages

| Middleware delegate chain

| Direct byte[] → cshttp → `switch(path)`
| | HTML Rendering | .aspx control trees & ViewState

| Razor compilation / Blazor | Two-phase C# $"" multiline strings
| | Session State | Locking SessionStateModule
| Distributed cache middleware | Lock-free ConcurrentDictionary + DB `lsid`
| | Runtime Compatibility | Windows .NET Framework 4.8 only | Modern .NET only (drops .NET 4.8) | Runs on both .NET 4.8 and `.NET 6/8/10+`
| | Code Longevity | Tied to IIS configuration | Subject to frequent major-version churn | Frozen, self-contained C# 7.3 source
|


7. Conclusion

Frameworks exist to solve general problems for average teams, but they extract a permanent tax in complexity, indirection, and dependency maintenance. By pairing Nginx at the network edge with a supervised C# `TcpListener, embedding **cshttp` for RFC-compliant HTTP/1.1 wire parsing, and structuring application logic around Pageless Architecture, developers reclaim complete ownership of their stack. Every byte from the network socket to the SQLite/MySQL database and back to the browser travels through readable, step-through-debuggable C# code that you own in full—with zero black boxes and zero external dependencies.

Would you like to expand any section of this white paper—such as adding the full reference implementation for the Graceful Shutdown TcpListener loop or the Linux systemd / Windows Service deployment blueprints—or export it as a formatted PDF/Markdown document?