Learn .NET - I/O & File Handling
Series/Learn .NET/Episode 7
Episode 7 of 23

Learn .NET - I/O & File Handling

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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.

System.IO and the File and Directory API

Reading and Writing Files

The System.IO namespace provides concise static helpers:

Reading and writing files
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.

Checking and Deleting Files

Always check that a file exists before operating on it:

Checking and deleting
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.

StreamReader, StreamWriter, and FileStream

Reading Large Files with Streams

For large files, reading everything at once will strain memory. Use a stream that reads line by line:

StreamReader 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:

StreamWriter writing lines
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.

When to Use a Stream

A decision guide:

  • File.ReadAllText: small files read in full.
  • StreamReader/StreamWriter: large files processed line by line.
  • FileStream: binary access, seek, and full buffer control.
  • MemoryStream: in-memory data you want to treat like a stream.

JSON Serialization with System.Text.Json

Serialize and Deserialize

System.Text.Json is a fast, built-in serializer:

JSON serialization
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.

Configuring Options

Options are built once and reused:

Serializer options
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.

Configuration Files and Environment Variables

Reading appsettings.json

ASP.NET Core and worker projects automatically load appsettings.json through the host. An example file:

appsettings.json
{
  "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 and Provider Order

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:

Reading configuration
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.

Healthy I/O Practices

A summary of practical rules:

  • Use Path.Combine and Path.GetFileName to stay cross-platform safe.
  • Choose streams for large files; one-call helpers for small files.
  • Check File.Exists before risky operations.
  • Serialize JSON with options stored as static.
  • Keep secrets in environment variables, not in committed files.

Closing

Key takeaways:

  • System.IO provides concise helpers for reading and writing files.
  • StreamReader and StreamWriter suit large files with low memory.
  • FileStream gives full control for binary data and seek.
  • System.Text.Json handles serialization and deserialization quickly.
  • appsettings.json loads automatically; env vars override it with the __ prefix.
  • Configuration is accessed through IConfiguration and GetConnectionString.

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.