The System.IO namespace provides all the tools needed to interact with the filesystem. The static File and Directory classes offer simple one-liner methods for the most common operations — reading and writing text, checking existence, creating directories — without requiring you to manage stream lifetimes manually. The Path class handles platform-specific path separator differences so your code works correctly on Windows (\), Linux (/), and macOS. File I/O appears throughout ASP.NET Core for configuration files, uploaded file storage, log file management, and export generation.
File Class — One-Liner Operations
// ── Reading files ──────────────────────────────────────────────────────────
string content = File.ReadAllText("config.json"); // entire file as string
string[] lines = File.ReadAllLines("data.csv"); // array of lines
byte[] bytes = File.ReadAllBytes("image.png"); // raw bytes
// ── Writing files ──────────────────────────────────────────────────────────
File.WriteAllText("output.txt", "Hello, World!"); // creates or overwrites
File.WriteAllLines("log.txt", new[] { "Line 1", "Line 2" }); // creates or overwrites
File.AppendAllText("log.txt", $"\n{DateTime.Now}: New entry"); // appends to existing
File.WriteAllBytes("copy.png", bytes); // write raw bytes
// ── File existence and metadata ────────────────────────────────────────────
bool exists = File.Exists("config.json"); // true if file exists
File.Delete("temp.txt"); // delete (no exception if not found)
File.Copy("source.txt", "dest.txt", overwrite: true); // copy with optional overwrite
File.Move("old.txt", "new.txt"); // rename / move
// FileInfo — object-oriented file metadata
var info = new FileInfo("data.csv");
Console.WriteLine($"Size: {info.Length:N0} bytes");
Console.WriteLine($"Created: {info.CreationTimeUtc:O}");
Console.WriteLine($"Modified: {info.LastWriteTimeUtc:O}");
Console.WriteLine($"ReadOnly: {info.IsReadOnly}");
File methods like ReadAllText internally open a FileStream, read the content, and close the stream — all in one call. They are convenient but load the entire file into memory. For large files (logs, CSV exports, binary assets), use streams (Lesson 2) to process data in chunks without loading everything into memory at once. A 500MB log file read with ReadAllText consumes 500MB of heap memory; reading it line by line with a StreamReader uses only a small buffer.Path.Combine() instead of string concatenation for building file paths. Path.Combine("uploads", "avatars", "user_42.jpg") produces the correct separator for the current OS — uploads\avatars\user_42.jpg on Windows and uploads/avatars/user_42.jpg on Linux. Hardcoding "\\" or "/" causes subtle failures when your ASP.NET Core application is deployed to Linux (Azure App Service, Docker containers). This is a very common portability bug in Windows-developed applications.../../appsettings.json or ../../../etc/passwd can allow an attacker to read or write files outside your intended directory. Always sanitise filenames: strip path separators, validate the extension, and verify the resolved absolute path is still within the expected base directory using Path.GetFullPath() and checking it starts with your upload root.Path Class — Cross-Platform Path Manipulation
// ── Combining paths ────────────────────────────────────────────────────────
string uploadDir = Path.Combine("wwwroot", "uploads", "avatars");
string filePath = Path.Combine(uploadDir, "user_42.jpg");
// ── Extracting parts of a path ─────────────────────────────────────────────
string file = @"C:\projects\blog\wwwroot\uploads\avatar.jpg";
Path.GetFileName(file) // "avatar.jpg"
Path.GetFileNameWithoutExtension(file) // "avatar"
Path.GetExtension(file) // ".jpg"
Path.GetDirectoryName(file) // "C:\projects\blog\wwwroot\uploads"
Path.GetFullPath("../config.json") // resolved absolute path
// ── Generating unique names ────────────────────────────────────────────────
string tempFile = Path.GetTempFileName(); // creates a 0-byte temp file
string randomName = Path.GetRandomFileName(); // "x3lk2m9.tmp" — no file created
string uniqueId = $"{Guid.NewGuid()}{Path.GetExtension(originalName)}";
// "3f2504e0-4f89-11d3-9a0c-0305e82c3301.jpg"
Directory Class
// ── Creating directories ───────────────────────────────────────────────────
Directory.CreateDirectory("uploads/avatars"); // creates all intermediate dirs too
// No exception if the directory already exists — safe to call repeatedly
// ── Checking existence ─────────────────────────────────────────────────────
bool dirExists = Directory.Exists("uploads");
bool fileExist = File.Exists("uploads/avatar.jpg");
// ── Listing contents ───────────────────────────────────────────────────────
string[] allFiles = Directory.GetFiles("uploads");
string[] jpgFiles = Directory.GetFiles("uploads", "*.jpg");
string[] allRecurse = Directory.GetFiles("uploads", "*", SearchOption.AllDirectories);
string[] subDirs = Directory.GetDirectories("uploads");
// DirectoryInfo — object-oriented metadata + operations
var dir = new DirectoryInfo("uploads");
foreach (FileInfo file in dir.GetFiles("*.jpg", SearchOption.AllDirectories))
Console.WriteLine($"{file.Name} ({file.Length:N0} bytes)");
Common Mistakes
Mistake 1 — Hardcoding path separators (breaks on Linux/macOS)
❌ Wrong — assumes Windows path separator:
string path = "uploads" + "\\" + "avatars" + "\\" + fileName; // breaks on Linux!
✅ Correct — use Path.Combine:
string path = Path.Combine("uploads", "avatars", fileName); // ✓ cross-platform
Mistake 2 — Using user input directly in file paths (path traversal vulnerability)
❌ Wrong — allows reading arbitrary files:
File.ReadAllText(Path.Combine(uploadsDir, userInput)); // userInput = "../../appsettings.json"!
✅ Correct — sanitise and validate the resolved path stays within the expected directory.