cshttp Tutorial - Day 11: Talking to the Server Without Reloading (`fetch` + Plain Text)
Day 11: Talking to the Server Without Reloading (fetch + Plain Text)
Up to Day 10, every single time we sent data to the server (clicking a link or submitting an HTML <form>), the entire web page reloaded!
Modern web applications, however, often update just one small box on the screen without ever reloading the page: a chat message pops up, a "Saved!" status label appears, or a live counter updates smoothly.
How does a web page talk to the C# server in the background without reloading? By using JavaScript's built-in fetch() function!
Today, we will build a live Shared Notepad on our home page (/, /home, or /notepad). We will keep the communication as simple as possible: JavaScript and our C# server will pass pure plain text back and forth!
In Day 11, we will:
- Start with a clean, minimal
Program.cswhere/,/home, and/notepadall open our Shared Notepad page. - Understand the difference between a Page Route (
/notepad) and an API Data Route (/api/note). - Create
HandleNoteApi()so our server can send and receive raw plain text (Request.BodyText). - Use
fetch('/api/note')in JavaScript to load and display the note without reloading the page. - Use
fetch('/api/note', { method: 'POST', body: ... })to save edited text back to the server in the background!
1. Two Kinds of Routes: Page Routes vs. API Routes
In our Shared Notepad app, our switch (path) statement will only need two main handlers (plus HandleNotFound()):
| Route | Who calls it? | What our C# server returns |
|---|---|---|
/, /home, /notepad | A human visitor opening the website | A complete HTML page (text/html) containing the text boxes, buttons, and <script> |
/api/note | JavaScript (fetch) running in the background | Only the raw plain text of the note (text/plain) — zero HTML tags! |
Why does
/api/notestart with/api/? In web development, a route that returns raw data for code (like JavaScript) instead of a full HTML page for humans is called an API endpoint. Naming it/api/notemakes it easy to see at a glance which routes are pages and which routes are data endpoints!
2. Starting with Our Clean Skeleton
Let's start with a clean Program.cs that stores our shared noteText in server memory (static string noteText) and routes /, /home, and /notepad to HandleNotepad():
using System;
using cshttp;
namespace ConsoleApp1
{
internal class Program
{
// NEW IN DAY 11: The shared notepad text stored in server memory!
static string noteText = "Hello! This is the shared notepad.\nEdit me and click Save.";
static void Main(string[] args)
{
WebApp.Run(8080, OnRequest);
}
static void OnRequest()
{
string path = Request.Path.ToLower();
switch (path)
{
case "/":
case "/home":
case "/notepad":
HandleNotepad();
break;
case "/api/note":
HandleNoteApi();
break;
default:
HandleNotFound();
break;
}
}
static void RenderPage(string title, string bodyContent)
{
string html = $@"<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
</head>
<body>
<nav>
<a href='/'>Home (Notepad)</a> |
<a href='/api/note' target='_blank'>View Raw /api/note</a>
</nav>
<hr />
{bodyContent}
<hr />
<footer>
<small>Server Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}</small>
</footer>
</body>
</html>";
Response.Write(html);
}
static void HandleNotFound()
{
Response.StatusCode = 404;
string content = @"
<h1>404 Not Found</h1>
<p>The page that you are looking for does not exist.</p>";
RenderPage("404 Not Found", content);
}
}
}
3. Building the /api/note Endpoint (HandleNoteApi)
Let's build HandleNoteApi() first! This method has two jobs depending on Request.HttpMethod:
- When
Request.HttpMethod == "GET"(JavaScript asks for the current note):We set
Response.ContentType = "text/plain";and sendnoteTextusingResponse.Write(noteText);. - When
Request.HttpMethod == "POST"(JavaScript sends updated note text to save):We read the raw text sent by the browser and store it in
noteText!
How Does the Server Read Raw Plain Text (Request.BodyText)?
In Day 4, when we submitted an HTML <form>, the browser formatted the data with field names (sender=Alice&message=Hello), so we read it using Request.Form["sender"].
Today, JavaScript's fetch() is going to send pure, raw text in the request body—no form field names, no =, and no &:
Buy milk
Buy bread
Call grandma
Because it is not a form, Request.Form is empty (0 fields). Instead, how do we read the raw text body in cshttp?
You can read it in one single line using Request.BodyText!
string text = Request.BodyText;
Good to Know —
Request.BodyTextvs.Request.InputStream: Behind the scenes,Request.BodyTextreads the raw bytes fromRequest.InputStreamand decodes them as UTF-8 text automatically. If you ever see older C# code usingnew StreamReader(Request.InputStream, Encoding.UTF8).ReadToEnd(),Request.BodyTextiscshttp's built-in one-line shortcut for that exact same operation!
Here is our complete HandleNoteApi() method:
// NEW IN DAY 11: Sends or receives PLAIN TEXT. No HTML at all!
static void HandleNoteApi()
{
// 1. POST: Read the raw text body sent by JavaScript and save it
if (Request.HttpMethod == "POST")
{
string text = Request.BodyText;
noteText = text;
Console.WriteLine("Note saved (" + text.Length + " characters)");
Response.ContentType = "text/plain";
Response.Write("Saved! (" + text.Length + " characters)");
return;
}
// 2. GET: Send the current note as plain text
Response.ContentType = "text/plain";
Response.Write(noteText);
}
4. Building HandleNotepad() — Part 1: The HTML Elements
Now let's build the /notepad (and /home) page! First, let's look at the HTML elements we need on the page (before we add any JavaScript):
<pre id='noteView'></pre>: A box to display the current note from the server (<pre>preserves spaces and line breaks).<button onclick='loadNote()'>Reload from server</button>: A button that calls a JavaScript function namedloadNote()when clicked.<textarea id='noteEditor'></textarea>: A multi-line text box where the visitor can edit the note.<button onclick='saveNote()'>Save to server</button>: A button that calls a JavaScript function namedsaveNote()when clicked.<span id='status'></span>: A green label next to the Save button where JavaScript can show"Saved! (42 characters)".
<h1>Shared Notepad</h1>
<p>Everyone visiting this website shares this one note.</p>
<div style='background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px;'>
<h2>1. The note (from the server)</h2>
<pre id='noteView' style='background: white; border: 1px solid #ccc; padding: 10px; min-height: 60px;'></pre>
<button onclick='loadNote()'>Reload from server</button>
</div>
<div style='background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px;'>
<h2>2. Edit the note</h2>
<textarea id='noteEditor' rows='6' style='width: 100%;'></textarea>
<br /><br />
<button onclick='saveNote()'>Save to server</button>
<span id='status' style='color: green; margin-left: 10px;'></span>
</div>
Notice that there is no <form> tag anywhere! Instead, our buttons use onclick='loadNote()' and onclick='saveNote()' to trigger JavaScript functions directly.
5. Building HandleNotepad() — Part 2: Loading Text with fetch() (GET)
Now let's write the <script> tag that powers loadNote()!
First, we grab references to our three HTML elements by their id:
var noteView = document.getElementById('noteView');
var noteEditor = document.getElementById('noteEditor');
var statusText = document.getElementById('status');
Next, how does loadNote() ask our C# server for /api/note in the background?
function loadNote() {
fetch('/api/note')
.then(function (response) {
return response.text(); // Step 1: Read the server's reply as plain text
})
.then(function (text) {
// Step 2: Put the text into our <pre> box and <textarea> editor!
noteView.textContent = text;
noteEditor.value = text;
});
}
Let's Trace Those 3 Steps in loadNote():
fetch('/api/note'): Sends a backgroundGETrequest tohttp://localhost:8080/api/note..then(function (response) { return response.text(); }): When the server replies,response.text()extracts the plain text from the reply..then(function (text) { ... }):noteView.textContent = text;puts the text inside the<pre id='noteView'>box. (Using.textContentin JavaScript is 100% safe against HTML injection—just likeWebApp.HtmlEncodein C#!)noteEditor.value = text;fills the<textarea id='noteEditor'>box with the same text so you can start editing it right away!
6. Building HandleNotepad() — Part 3: Saving Text with fetch() (POST)
Finally, what happens when the visitor edits the <textarea> and clicks Save to server (saveNote())?
To make fetch() send a POST request instead of a GET request, we pass a second parameter { method: 'POST', body: noteEditor.value }:
function saveNote() {
fetch('/api/note', {
method: 'POST',
body: noteEditor.value // Send the textarea's text as the raw request body!
})
.then(function (response) {
return response.text();
})
.then(function (reply) {
statusText.textContent = reply; // Show "Saved! (42 characters)"
loadNote(); // Refresh the <pre> box with the saved note
});
}
Here is a visual diagram of how JavaScript in the browser and C# on the server talk to each other without ever reloading the page:
Browser (JavaScript) C# Server (HandleNoteApi)
──────────────────── ─────────────────────────
1. loadNote():
fetch('/api/note') ── GET ───▶ Response.Write(noteText)
noteView.textContent = text ◀── text ──
2. saveNote():
fetch('/api/note', {
method: 'POST',
body: noteEditor.value ── POST ──▶ noteText = Request.BodyText
}) Response.Write("Saved! ...")
statusText.textContent = ... ◀── reply ─
7. Complete Code for Day 11
Here is the complete, self-contained Program.cs for Day 11:
using System;
using cshttp;
namespace ConsoleApp1
{
internal class Program
{
// NEW IN DAY 11: The shared notepad text stored in server memory
static string noteText = "Hello! This is the shared notepad.\nEdit me and click Save.";
static void Main(string[] args)
{
WebApp.Run(8080, OnRequest);
}
static void OnRequest()
{
string path = Request.Path.ToLower();
switch (path)
{
case "/":
case "/home":
case "/notepad":
HandleNotepad();
break;
case "/api/note":
HandleNoteApi();
break;
default:
HandleNotFound();
break;
}
}
static void RenderPage(string title, string bodyContent)
{
string html = $@"<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
</head>
<body>
<nav>
<a href='/'>Home (Notepad)</a> |
<a href='/api/note' target='_blank'>View Raw /api/note</a>
</nav>
<hr />
{bodyContent}
<hr />
<footer>
<small>Server Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}</small>
</footer>
</body>
</html>";
Response.Write(html);
}
// NEW IN DAY 11: Sends or receives PLAIN TEXT. No HTML at all!
static void HandleNoteApi()
{
if (Request.HttpMethod == "POST")
{
// Read the raw text body sent by JavaScript's fetch()
string text = Request.BodyText;
noteText = text;
Console.WriteLine("Note saved (" + text.Length + " characters)");
Response.ContentType = "text/plain";
Response.Write("Saved! (" + text.Length + " characters)");
return;
}
// GET: send the current note as plain text
Response.ContentType = "text/plain";
Response.Write(noteText);
}
// NEW IN DAY 11: The Notepad page. JavaScript talks to /api/note without reloading the page!
static void HandleNotepad()
{
// Notice we use a normal @"..." string (without $), so JavaScript's { } braces don't need doubling!
string content = @"
<h1>Shared Notepad</h1>
<p>Everyone visiting this website shares this one note.</p>
<div style='background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px;'>
<h2>1. The note (from the server)</h2>
<pre id='noteView' style='background: white; border: 1px solid #ccc; padding: 10px; min-height: 60px;'></pre>
<button onclick='loadNote()'>Reload from server</button>
</div>
<div style='background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px;'>
<h2>2. Edit the note</h2>
<textarea id='noteEditor' rows='6' style='width: 100%;'></textarea>
<br /><br />
<button onclick='saveNote()'>Save to server</button>
<span id='status' style='color: green; margin-left: 10px;'></span>
</div>
<script>
var noteView = document.getElementById('noteView');
var noteEditor = document.getElementById('noteEditor');
var statusText = document.getElementById('status');
// 1. Ask the server for the note (GET), then show it
function loadNote() {
fetch('/api/note')
.then(function (response) {
return response.text(); // read the reply as plain text
})
.then(function (text) {
noteView.textContent = text; // textContent = safe, shown exactly as-is
noteEditor.value = text;
});
}
// 2. Send the textarea's text to the server (POST), then reload it
function saveNote() {
fetch('/api/note', {
method: 'POST',
body: noteEditor.value // the raw text IS the request body
})
.then(function (response) {
return response.text();
})
.then(function (reply) {
statusText.textContent = reply; // e.g. 'Saved! (42 characters)'
loadNote();
});
}
// Load the note automatically as soon as the page opens
loadNote();
</script>";
RenderPage("Shared Notepad", content);
}
static void HandleNotFound()
{
Response.StatusCode = 404;
string content = @"
<h1>404 Not Found</h1>
<p>The page that you are looking for does not exist.</p>";
RenderPage("404 Not Found", content);
}
}
}
8. Test It in Your Browser
- Run the application (
dotnet runor press Start in Visual Studio) and visithttp://localhost:8080/. - First, click View Raw /api/note in the navigation bar (or visit
http://localhost:8080/api/note). Notice that you see only the raw text of the note—no HTML, no navigation bar, and no footer! That is whatfetch('/api/note')reads behind the scenes. - Go back to
http://localhost:8080/. Look at the Server Time in the footer at the bottom of the page, and remember the seconds shown on the clock. - Now type a shopping list into the 2. Edit the note box:
Buy milk Buy bread Call grandma - Click Save to server.
- Look closely at what happened:
- The green label says
Saved! (31 characters). - Box 1 (The note from the server) immediately updated to show your shopping list!
- Now look at the Server Time clock in the footer at the bottom: the time did NOT change! That proves the entire page never reloaded—JavaScript updated the note in the background using
fetch()!
- The green label says
- Open a second browser window at
http://localhost:8080/—your shopping list is right there too, shared across everyone visiting the server!
9. Quick Reference Summary
| Goal | Code |
|---|---|
| C# Server: Send plain text | Response.ContentType = "text/plain";Response.Write(noteText); |
| C# Server: Read raw plain text body | string text = Request.BodyText; |
JavaScript: Request plain text (GET) | fetch('/api/note').then(function(r) { return r.text(); }) |
JavaScript: Send plain text (POST) | fetch('/api/note', { method: 'POST', body: text }) |
| JavaScript: Safely display text in HTML | element.textContent = text; (prevents HTML/XSS injection!) |
In Day 12, we will learn how to send structured objects and lists (like a list of comments where each item has a separate name, message, and time) between C# and JavaScript using JSON!