Files, settings and persistence
OpenFileDialog and SaveFileDialog, JSON configuration, user settings, recent-file lists, and the file-handling rules that keep a desktop app trustworthy.
File dialogs
using var open = new OpenFileDialog
{
Title = "Open data file",
Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*",
FilterIndex = 1,
Multiselect = true,
CheckFileExists = true,
RestoreDirectory = true
};
if (open.ShowDialog(this) != DialogResult.OK) return;
foreach (var path in open.FileNames)
queue.Enqueue(path);
using var save = new SaveFileDialog
{
Filter = "JSON report (*.json)|*.json",
FileName = $"report-{DateTime.Today:yyyy-MM-dd}",
DefaultExt = "json",
AddExtension = true,
OverwritePrompt = true
};
if (save.ShowDialog(this) == DialogResult.OK)
File.WriteAllText(save.FileName, JsonSerializer.Serialize(report, JsonOptions));- On modern Windows the common dialog offers a virtual location rather than a real path. Check
File.Existsbefore opening, and handle the emptyFileNamethat some shells return. RestoreDirectory = trueputs the working directory back afterwards; without it a relative path used later silently points somewhere else.- Filenames and paths are not sanitised for you. A file named
CON.txtor a path with a trailing dot fails on real Windows.
Settings and configuration
public sealed class AppSettings
{
public string Theme { get; set; } = "Light";
public int RecentCount { get; set; } = 50;
public List<string> RecentFiles { get; set; } = new();
}
static string SettingsPath => Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"OrderTool", "settings.json");
public static AppSettings Load()
{
try
{
return File.Exists(SettingsPath)
? JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(SettingsPath)) ?? new AppSettings()
: new AppSettings();
}
catch (JsonException)
{
File.Move(SettingsPath, SettingsPath + ".bad", overwrite: true); // keep the evidence
return new AppSettings(); // and start clean
}
}
public static void Save(AppSettings s)
{
Directory.CreateDirectory(Path.GetDirectoryName(SettingsPath)!);
var tmp = SettingsPath + ".tmp";
File.WriteAllText(tmp, JsonSerializer.Serialize(s, new JsonSerializerOptions { WriteIndented = true }));
File.Move(tmp, SettingsPath, overwrite: true); // write-then-rename, never a half file
}| What to store | Where | Why |
|---|---|---|
| Machine configuration | appsettings.json beside the executable | Ships with the app, read-only at runtime |
| Per-user preferences | %APPDATA% | Writable without administrator rights |
| Per-user cached data | %LOCALAPPDATA% | Not synced to a roaming profile |
| Secrets | Windows Credential Manager or DPAPI | A JSON file is readable by anyone with the profile |
| Documents | Documents or a user-chosen path | Users expect to find their own files |
Write settings atomically: serialise to a temporary file in the same directory, then rename over the target. A crash during a direct write leaves a truncated file behind, and the user's next launch is the one that breaks.
Recent files and safe handling
void RememberFile(string path)
{
var recent = _settings.RecentFiles;
recent.RemoveAll(p => string.Equals(p, path, StringComparison.OrdinalIgnoreCase));
recent.Insert(0, path);
if (recent.Count > _settings.RecentCount)
recent.RemoveRange(_settings.RecentCount, recent.Count - _settings.RecentCount);
AppSettings.Save(_settings);
RebuildRecentMenu();
}- Open with
FileShare.Readwhen you only read, so another process is not blocked. - Use
Path.GetFullPathbefore comparing paths; the same file has many spellings. - Skip entries in the recent list that no longer exist, but keep them until the user asks to clean up - a disconnected drive is not a deleted file.
- Never construct a path by concatenating user input: use
Path.Combineand reject any result that escapes the intended directory. - Log the exception type and the path, never the file contents, when a save fails.
⚠️
A desktop app that crashes on a corrupt settings file is a support ticket you will never close. Treat every file you read at startup as untrusted: wrap it, validate it, and fall back to defaults so the window always opens.
FAQ
Application settings or user settings?
Application settings are shared and read-only at runtime; user settings are per profile and writable. In .NET 8 and later, prefer a JSON file in
%APPDATA% over the legacy settings designer - it is visible, diffable and testable.How do I store a password?
Do not. Use
CredentialManager or protect the value with DPAPI scoped to the current user, and only ever store a secret you cannot replace with a token.Related
Async work, threading and progress Deployment: ClickOnce, MSIX and single-file
Last refreshed 2026-09-18.