This episode breaks down file and data management in .NET: the System.IO API for reading and writing files, StreamReader, StreamWriter, and FileStream for large data, JSON serialization with System.Text.Json, and reading appsettings.json and environment variables.

Almost every application touches files: reading configuration, writing logs, or processing imported data. Episode 7 breaks down I/O and file handling in .NET — from the simple System.IO API, to streams for large data, JSON serialization, and integration with the configuration system.
Here is the key difference you will learn: one-shot APIs like File.ReadAllText are convenient for small files, while streams are a must when handling large files so memory does not blow up. And for modern data exchange, JSON is the primary format.
The System.IO namespace provides concise static helpers:
var path = Path.Combine("data", "catatan.txt");
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllText(path, "Hello dari file");
string isi = File.ReadAllText(path);
Console.WriteLine(isi);File.WriteAllText and File.ReadAllText handle the entire file lifecycle in a single call. Path.Combine builds cross-platform paths correctly — avoid joining paths manually with slashes, because it will break on Windows.
Always check that a file exists before operating on it:
if (File.Exists(path))
{
File.Delete(path);
}
if (Directory.Exists("data"))
{
Console.WriteLine("Folder data ada");
}File.Exists(path) and Directory.Exists return booleans that are safe to use as guards before I/O operations. Combine them with the try/catch blocks from episode 6 to handle denied access or locked files.
For large files, reading everything at once will strain memory. Use a stream that reads line by line:
using var reader = new StreamReader("log.txt");
while (await reader.ReadLineAsync() is string baris)
{
Console.WriteLine(baris);
}StreamReader.ReadLineAsync reads one line at a time — memory stays small even with millions of lines. Writing with StreamWriter is similar:
await using var writer = new StreamWriter("out.txt");
await writer.WriteLineAsync("baris pertama");await using var writer combines automatic disposal with asynchronous processing. FileStream gives the lowest-level control — pointer position, buffering, and access modes — suitable for binary and custom formats.
A decision guide:
System.Text.Json is a fast, built-in serializer:
public record User(string Nama, int Umur);
var user = new User("Arman", 30);
string json = JsonSerializer.Serialize(user);
User balik = JsonSerializer.Deserialize<User>(json);
Console.WriteLine(balik.Nama);JsonSerializer.Serialize(user) turns an object into JSON, and Deserialize<User> turns it back. The default behavior uses plain property names — you can customize this through JsonSerializerOptions, for example PropertyNamingPolicy = JsonNamingPolicy.CamelCase for consistency with web APIs.
Options are built once and reused:
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string cantik = JsonSerializer.Serialize(user, options);WriteIndented = true produces human-readable indented JSON. For the highest performance, avoid creating new options on every call — store them as static.
ASP.NET Core and worker projects automatically load appsettings.json through the host. An example file:
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"ConnectionStrings": {
"Default": "Server=localhost;Database=App"
}
}Configuration is read through IConfiguration, which is injected into the application. Sensitive values such as database connections should live in environment variables so they are never committed to Git.
Environment variables override appsettings.json when keys collide. The naming rule: sections are separated by __ (double underscore). So ConnectionStrings__Default maps to ConnectionStrings:Default. Read the value in code:
var koneksi = builder.Configuration.GetConnectionString("Default");
Console.WriteLine(koneksi);builder.Configuration.GetConnectionString("Default") retrieves the value from the matching provider. The provider order — JSON files, then environment variables, then command line — determines who gets to override. We will dig into the details in episode 10.
Tip
Don't hardcode file paths or connection strings. Read them from configuration, and let environment variables override the values at deployment time.
A summary of practical rules:
Key takeaways:
In the next episode 8 we will discuss dependency injection and service lifetimes — the Microsoft.Extensions.DependencyInjection container, scoped, transient, and singleton lifetimes, service registration patterns, and configuring services inside the Generic Host.