BACK TO BLOG
2026-09-05Meghmalhar Bhowmick

190 Lines of C# to Outsmart School IT

C#.NETWeb ServerIndradhanushOfflineWindowsCreative Problem Solving

In August 2026, the Indradhanush Interact Club at GD Goenka Public School, Dakshineswar, was scheduled for its annual exhibition. I had built the club's full web platform — event management, member profiles, photo galleries — as a Next.js application backed by Neon Postgres and Clerk authentication. It was live at a production URL.

And then I thought about what would happen during the exhibition.

The school computer lab has no reliable internet connection. It has Windows PCs with no administrative privileges for students. You can't install Node.js. You can't install Python. You can't run Docker. You can't open a built HTML file directly in a browser because modern ES modules and absolute path routing break under the file:// protocol (CORS policy, module resolution, everything fails).

I needed a way to run the full platform — with proper routing, static asset serving, and SPA fallback — on any lab PC, from a USB drive, with zero installation required.

What's already installed on every Windows PC

Every modern Windows machine ships with the .NET Framework. You don't need to install anything to compile and run C#. A console application compiled to a single .exe file runs everywhere, carries no dependencies, and fits in a few hundred kilobytes on a thumb drive.

So I wrote indradhanush/Server.cs: a 190-line zero-dependency C# HTTP web server.

The core server loop

using System.Net;
using System.Threading;

HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://localhost:" + _port + "/");
listener.Prefixes.Add("http://127.0.0.1:" + _port + "/");
listener.Start();

ThreadPool.QueueUserWorkItem((o) =>
{
    while (listener.IsListening)
    {
        try
        {
            var context = listener.GetContext();
            ThreadPool.QueueUserWorkItem(
                (c) => ProcessRequest((HttpListenerContext)c),
                context
            );
        }
        catch { }
    }
});

System.Net.HttpListener is built into the .NET Framework and runs without any third-party packages. Incoming HTTP connections get dispatched to ThreadPool worker threads via QueueUserWorkItem, so multiple students can browse the platform concurrently without request queuing. The main thread stays responsive to the stop signal.

Static asset serving and MIME types

ProcessRequest resolves incoming URL paths to the static export directory (site/) and streams the file bytes directly to the HTTP response:

byte[] buffer = File.ReadAllBytes(filePath);
context.Response.ContentType = mime;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);

The MIME map handles 16 types including .woff2, .ttf, .svg, .json, and .webp — everything Next.js's static export generates. All lookups are case-insensitive so Script.js and script.js resolve correctly regardless of URL casing.

The SPA fallback

The most important part: client-side routing. When the Next.js app navigates to /projects or /team, the browser requests that URL from the server. A naive file server would return 404 because there's no projects.html in the export directory — Next.js uses JavaScript to handle those routes client-side.

The fix is the SPA fallback: if a requested path doesn't resolve to an existing file, stream index.html with HTTP 200.

string filePath = ResolveFilePath(decodedPath);
if (filePath != null && File.Exists(filePath))
{
    // Serve static asset
    byte[] buffer = File.ReadAllBytes(filePath);
    context.Response.OutputStream.Write(buffer, 0, buffer.Length);
}
else
{
    // SPA fallback: serve index.html for unknown client routes
    string indexPath = Path.Combine(_rootDirectory, "index.html");
    if (File.Exists(indexPath))
    {
        context.Response.ContentType = "text/html; charset=utf-8";
        byte[] buffer = File.ReadAllBytes(indexPath);
        context.Response.OutputStream.Write(buffer, 0, buffer.Length);
    }
}

Every client-side route (/projects, /team, /events/diwali-drive) returns index.html and the React router mounts the right component. From the user's perspective, navigation works exactly as it would on the live production site.

What it looked like on exhibition day

The USB drive had two things: IndradhanushOfflineServer.exe and the site/ directory (the Next.js static export). Double-click the executable, open a browser, navigate to localhost:8000. The full club platform loads instantly — photos, events, members, the whole thing — with no internet, no Node.js, no admin rights, and no configuration.

We ran it on four lab PCs simultaneously during the exhibition. No crashes. Principal Ms. Vijaylaxmi Kumar and the visiting Rotary District dignitaries saw a fully functional web platform on a machine that had no internet connection.

Takeaway

The most powerful tool is the one already installed on your deployment target. When institutional firewalls block modern web tooling, standard-library languages native to the OS can replicate your entire server infrastructure in under 200 lines of code. You don't need a framework. You don't need a package manager. You need System.Net.HttpListener and the confidence that .NET ships with Windows.

[ GALLERY ]