Princippet: YourName\AppName
Et program skal næsten altid gemme noget: brugerens
indstillinger eller rigtige data som f.eks. en SQLite-database.
Konventionen er at oprette sin egen undermappe i brugerens
profilmappe — bygget op som
YourName\AppName:
C:\Users\mje\AppData\Roaming\YourName\MyApp\ (Windows, indstillinger)
C:\Users\mje\AppData\Local\YourName\MyApp\ (Windows, data)
C:\Users\mje.HEG\Documents\YourName\MyApp\ (Windows, synlige filer)
/home/mje/.config/YourName/MyApp/ (Linux)
/Users/mje/Library/Application Support/YourName/MyApp/ (macOS)
YourName og AppName. Undgå mellemrum,
punktummer, bindestreger, æ/ø/å og andre specialtegn — mappenavne
med specialtegn giver problemer på tværs af platforme, kodninger
og scripts. MiJenner\Quizzer er fint —
M. Jenner\Min Quiz-App æøå er ikke.
C:\Program Files\... har en almindelig
bruger ikke skriveadgang — programmet fejler eller kræver
administratorrettigheder. Data, der ændrer sig mens programmet kører,
hører til i brugerens profilmappe (eller Dokumenter, hvis brugeren
selv skal kunne finde filen).
Hvor finder man mappen på de tre platforme?
Man skriver aldrig stierne ovenfor direkte ind i koden — .NET finder dem for dig, uanset hvad brugeren hedder, og uanset platform:
using System;
// Roaming: indstillinger der gerne må følge brugeren mellem maskiner
string roaming = Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData);
// Local: data der hører til denne maskine (f.eks. en SQLite-database)
string local = Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData);
// Dokumenter: filer brugeren selv skal kunne finde og åbne
string docs = Environment.GetFolderPath(
Environment.SpecialFolder.MyDocuments);
SpecialFolder |
Windows | macOS Linux |
|---|---|---|
ApplicationData |
%APPDATA% ( AppData\Roaming) |
~/Library/Application Support ~/.config |
LocalApplicationData |
%LOCALAPPDATA% ( AppData\Local) |
~/Library/Application Support ~/.local/share |
MyDocuments |
Dokumenter |
~/Documents |
En lille hjælpeklasse: AppFolders
For at komme hurtigt i gang med selve applikationens funktionalitet, kan man kopiere denne statiske klasse direkte ind i sit projekt. Den beslutter hvor filerne ligger — resten af programmet skal ikke tænke over det:
using System;
using System.IO;
namespace YourNamespace;
public static class AppFolders
{
// ── Tilpas disse to til dig og din app ──────────────────
// Kun bogstaver og tal — INGEN specialtegn/mellemrum!
private const string YourName = "MiJenner";
private const string AppName = "MyApp";
// ── Vælg strategi her: AppData (skjult) eller Documents (synlig) ──
public static string SettingsFolder =>
GetFolder(Environment.SpecialFolder.ApplicationData);
// GetFolder(Environment.SpecialFolder.LocalApplicationData);
// GetFolder(Environment.SpecialFolder.MyDocuments);
public static string DataFolder =>
GetFolder(Environment.SpecialFolder.ApplicationData);
// GetFolder(Environment.SpecialFolder.LocalApplicationData);
// GetFolder(Environment.SpecialFolder.MyDocuments);
// ── Praktiske genveje ───────────────────────────────────
public static string SettingsFile =>
Path.Combine(SettingsFolder, "usersettings.json");
public static string DatabaseFile =>
Path.Combine(DataFolder, "database.sqlite");
// ── Selve maskineriet ───────────────────────────────────
private static string GetFolder(Environment.SpecialFolder baseFolder)
{
string path = Path.Combine(
Environment.GetFolderPath(baseFolder),
YourName, AppName);
Directory.CreateDirectory(path); // opretter, hvis den ikke findes
return path;
}
}
- Højreklik på projektnavnet i Solution Explorer og vælg Add > Class…
- Giv den navnet
AppFolders.cs - Indsæt koden ovenfor — og ret
namespace YourNamespacetil dit eget projekts namespace (samme som iProgram.cs) - Tilpas
YourNameogAppNameøverst i klassen (måske også database- og settings-filnavnene).
Ændringer sker ét sted: skal indstillingerne i stedet ligge i
Dokumenter, kommenteres én linje ud, og en anden ind — og hele
programmet flytter med. Resten af koden bruger blot
AppFolders.SettingsFile og
AppFolders.DatabaseFile og kender slet ikke stien.
Path.Combine sætter selv det
rigtige skilletegn ind (\ på Windows, / på
macOS/Linux). Saml derfor altid stier med
Path.Combine — aldrig med strenge som
"YourName\MyApp", der kun virker på Windows.
AppData-mappe er som udgangspunkt skjult i
Stifinder, og ~/.config er usynlig på Linux.
Tre måder at komme derhen:
-
Windows: tast Win+R og
skriv
%APPDATA%— eller slå Vis skjulte elementer til i Stifinders Vis-menu. -
macOS: i Finder vælg Gå → Gå til
mappe… (⌘⇧G) og skriv
~/Library/Application Support. -
Linux:
cd ~/.config— ellerls -afor at se skjulte mapper. -
Fra selve programmet (cross platform) —
udskriv stien, eller åbn mappen direkte:
// Udskriv stien, så man kan kopiere den Console.WriteLine(AppFolders.DataFolder); // Åbn mappen i Stifinder (Windows) System.Diagnostics.Process.Start("explorer.exe", AppFolders.DataFolder); // Åbn mappen i Finder (macOS) System.Diagnostics.Process.Start("open", AppFolders.DataFolder); // Åbn mappen i filhåndteringen (Linux) System.Diagnostics.Process.Start("xdg-open", AppFolders.DataFolder);
YourName og AppName må kun indeholde
almindelige bogstaver og tal. Ingen specialtegn og
mellemrum — heller ikke æ, ø og å.
Brugerindstillinger som JSON
Med klassen på plads er det selve gemme/hente-logikken, man skal koncentrere sig om — JSON-serialisering er indbygget i .NET:
using System.IO;
using System.Text.Json;
namespace YourNamespace;
public class UserSettings
{
public string Theme { get; set; } = "light";
public string Language { get; set; } = "da";
}
- Tilføj
UserSettings-klassen i egen fil med navnetUserSettings.cs, og giv den ovenstående indhold. Retnamespace YourNamespacetil det namespace, du bruger i dit projekt. - Der kræves ingen NuGet-pakker —
System.Text.Jsoner indbygget i .NET
Gemme/hente-koden skrives i Program.cs i
Main-metoden, hvor det passer ind i programmets flow — f.eks.
hent ved opstart og gem når brugeren
ændrer en indstilling:
using System;
using System.IO;
using System.Text.Json;
namespace YourNamespace;
internal class Program
{
static void Main(string[] args)
{
// --- gem ---
var settings = new UserSettings { Theme = "dark" };
string json = JsonSerializer.Serialize(settings,
new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(AppFolders.SettingsFile, json);
// --- hent ---
UserSettings settings2 = File.Exists(AppFolders.SettingsFile)
? JsonSerializer.Deserialize<UserSettings>(
File.ReadAllText(AppFolders.SettingsFile)) ?? new UserSettings()
: new UserSettings();
}
}
Første gang programmet køres, findes filen ikke — så bruges klassens standardværdier, og filen oprettes, når brugeren ændrer en indstilling.
SQLite-databasens placering
En SQLite-database er én enkelt fil — og med hjælpeklassen er
placeringen allerede afgjort. Selve connection-strengen bygges
direkte på AppFolders.DatabaseFile:
string connectionString =
$"Data Source={AppFolders.DatabaseFile}";
Connection-strengen bruges med Microsoft.Data.Sqlite
eller oven på Dapper — se
Dapper cheat sheet.
SQLite skaber selv filen ved første forbindelse, og mappen er
allerede oprettet af AppFolders.
- Tilføj NuGet-pakken
Microsoft.Data.Sqlite(højreklik på projektet → Manage NuGet Packages…) — ellerDapper+Microsoft.Data.Sqlite, hvis du bruger Dapper - Skriv connection-streng-linjen i
Program.cs, før du åbner forbindelsen — resten er almindelig SQL/Dapper-kode - Filen
database.sqliteoprettes automatisk ved første forbindelse — du skal ikke lave den selv
Test-app: virker det på din computer?
Virksomheder og organisationer spænder ofte ben med gruppepolitikker, OneDrive-omdirigering af Dokumenter og skrivebeskyttede mapper. Denne lille konsol-app tester alle tre placeringer — AppData\Roaming, AppData\Local og Dokumenter — og rapporterer, hvad der faktisk virker. Testen har to trin for hver placering: først oprettes mappen, dernæst skrives en rigtig test-fil i mappen, som læses tilbage og sammenlignes. Det er to forskellige rettigheder (især på Windows), så begge trin skal sige OK. Opret projektet og kør det:
dotnet new console -n FolderTest
cd FolderTest
dotnet run
Erstat indholdet af Program.cs med dette (hjælpeklassen
er inkluderet nederst):
using System;
using System.IO;
namespace FolderTest;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Test af skrive/læse-adgang til app-mapper");
Console.WriteLine(new string('=', 55));
Console.WriteLine();
TestFolder("AppData\\Roaming",
Environment.SpecialFolder.ApplicationData);
TestFolder("AppData\\Local",
Environment.SpecialFolder.LocalApplicationData);
TestFolder("Dokumenter",
Environment.SpecialFolder.MyDocuments);
}
static void TestFolder(string label,
Environment.SpecialFolder baseFolder)
{
// ── Trin 1: kan mappen oprettes? ────────────────────
string folder;
try
{
folder = AppFolders.GetTestFolder(baseFolder);
Console.WriteLine($"{label}: mappe OK");
}
catch (Exception ex)
{
Console.WriteLine($"{label}: MAPPE-FEJL — {ex.Message}");
Console.WriteLine();
return;
}
// ── Trin 2: kan der skrives og læses en fil i mappen? ─
string testFile = Path.Combine(folder, "write-test.tmp");
string content = $"Test {DateTime.Now:HH:mm:ss}";
try
{
File.WriteAllText(testFile, content);
string readBack = File.ReadAllText(testFile);
File.Delete(testFile);
bool ok = readBack == content;
Console.WriteLine(ok
? $"{label}: fil OK"
: $"{label}: FIL-FEJL — indhold matcher ikke");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine($"{label}: INGEN FIL-SKRIVEADGANG");
}
catch (Exception ex)
{
Console.WriteLine($"{label}: FIL-FEJL — {ex.Message}");
}
Console.WriteLine($" {folder}");
Console.WriteLine();
}
}
// ── Hjælpeklassen — samme som ovenfor ────────────────────
public static class AppFolders
{
// Kun bogstaver og tal — INGEN specialtegn/mellemrum!
private const string YourName = "MiJenner";
private const string AppName = "FolderTest";
public static string SettingsFolder =>
GetFolder(Environment.SpecialFolder.ApplicationData);
// GetFolder(Environment.SpecialFolder.MyDocuments);
public static string DataFolder =>
GetFolder(Environment.SpecialFolder.LocalApplicationData);
// GetFolder(Environment.SpecialFolder.MyDocuments);
public static string SettingsFile =>
Path.Combine(SettingsFolder, "usersettings.json");
public static string DatabaseFile =>
Path.Combine(DataFolder, "appdata.db");
// Bruges af testen ovenfor — eksponerer alle tre placeringer
public static string GetTestFolder(Environment.SpecialFolder baseFolder) =>
GetFolder(baseFolder);
private static string GetFolder(Environment.SpecialFolder baseFolder)
{
string path = Path.Combine(
Environment.GetFolderPath(baseFolder),
YourName, AppName);
Directory.CreateDirectory(path);
return path;
}
}
Eksempel på output:
Test af skrive/læse-adgang til app-mapper
=======================================================
AppData\Roaming: mappe OK
AppData\Roaming: fil OK
C:\Users\mje\AppData\Roaming\MiJenner\FolderTest
AppData\Local: mappe OK
AppData\Local: fil OK
C:\Users\mje\AppData\Local\MiJenner\FolderTest
Dokumenter: mappe OK
Dokumenter: INGEN FIL-SKRIVEADGANG
C:\Users\mje.HEG\Documents\MiJenner\FolderTest
AppFolders. Fejler alle tre, er problemet typisk
antivirus eller en særlig stram gruppepolitik.
Testfilen write-test.tmp slettes igen efter hver test;
mappen bliver liggende.
Huskeregler
-
Aldrig hardcodede stier som
C:\Users\mje\...— brugEnvironment.GetFolderPath. -
Saml stier med
Path.Combine, ikke med strenge og\. -
Strukturér som
YourName\AppName— kun bogstaver og tal: ingen specialtegn og mellemrum (heller ikke æ, ø, å), ellers går det galt på tværs af platforme. - Test adgangen med konsol-appen ovenfor, før du vælger strategi — især på maskiner, en organisation administrerer.
- Vælg strategi ét sted: AppData (skjult for brugeren, ingen risiko for at filen slettes ved en fejl) eller Dokumenter (synlig — brugeren kan selv åbne, redigere og tage backup).
- Dokumenter-faldgruben: mappen synkroniseres ofte af OneDrive/iCloud. Fint til små JSON-indstillinger, men en åben SQLite-database kan komme i konflikt med synkroniseringen.
- Mobil er særligt: Android og iOS giver appen sin egen sandbox-mappe — se MAUI, Avalonia, eller Uno-platform-materialet i stedet.